предисловие
Только лысая голова может стать сильнее.
Текст был включен в мой репозиторий GitHub, добро пожаловать, звезда:GitHub.com/Zhongf UC очень…
На второй день нового года друг задал мне технический вопрос (мой друг очень хочет учиться, я восхищаюсь!)
Источник проблемы знает (проблема синхронизированной блокировки):
Откройте 10 000 потоков, и каждый поток добавляет 1 в поле денег таблицы сотрудников [начальное значение равно 0]. Пессимистические блокировки и оптимистичные блокировки не используются, но в метод бизнес-уровня добавляется ключевое слово synchronized. Проблема в том, что БД выполняется после выполнения кода.Поле денег в не 10000, а меньше 10000 В чем проблема?
Код сервисного уровня:
Код SQL (без пессимистической/оптимистической блокировки):
Запустите код с 1000 потоков:
Проще говоря: многопоточность запускает один с использованиемsynchronizedМетод с измененным ключевым словом работает с базой данных.По нормальной логике конечное значение должно быть 1000, но после многих тестов результатниже чем1000. Почему это?
1. Мое мышление
Так как результат теста ниже 1000, значит этот кодне потокобезопасныйиз. Это не потокобезопасно, так в чем проблема? Как мы все знаем, синхронизированный метод может гарантировать изменение代码块、方法
гарантировать有序性、原子性、可见性
.
Чтобы быть разумным, приведенный выше код работает, проблемаService
слоистыйincreaseMoney()
да有序的、原子的、可见的
,такутверждатьС синхронизацией должно быть все в порядке.
(Обратитесь к примечаниям по блокировке синхронизации, которые я написал ранее:Механизм блокировки Java для понимания)
Поскольку на уровне Java причину найти не удается, давайте проанализируем уровень базы данных (потому что база данных работает внутри метода). существуетincreaseMoney()
добавлен перед методом@Transcational
Аннотация, указывающая, что этот метод сделаиз. Транзакции гарантируют, что SQL-запросы в одной и той же группе будут успешными или ошибочными одновременно. Чтобы быть разумным, если нет ошибки, каждый поток должен выполнять денежное значение+1
. Теоретически результат должен быть 1000.
(Ссылаясь на транзакции Spring, которые я писал ранее:Эта статья поможет вам разобраться в весенних делах!)
Основываясь на приведенном выше анализе, я подозреваю, чтоСпрашивающий плохо проверил(hhhh, побег), так что я тоже побежал его тестировать и обнаружил, что он использовался на пути вопрошающегодействительно проблема.
Сначала опубликуйте мой тестовый код:
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@RequestMapping("/add")
public void addEmployee() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> employeeService.addEmployee()).start();
}
}
}
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Transactional
public synchronized void addEmployee() {
// 查出ID为8的记录,然后每次将年龄增加一
Employee employee = employeeRepository.getOne(8);
System.out.println(employee);
Integer age = employee.getAge();
employee.setAge(age + 1);
employeeRepository.save(employee);
}
}
Просто печатайте значение сотрудника, полученное каждый раз, и получайте порядок выполнения SQL следующим образом (отправьте небольшую часть):
Из ситуации с печатью можно сделать вывод, что в случае многопоточности инет серийного номеравоплощать в жизньaddEmployee()
метод. Это приводит к выполнениюповторение, поэтому окончательное значение меньше 1000.
Во-вторых, причина появления диаграммы
найдено неСинхронизироватьКазнен, так что я подозреваюsynchronized
Определенно существует конфликт между ключевыми словами и Spring. Поэтому я искал эти два ключевых слова и нашел проблему.
Мы знаем, что нижний уровень транзакции Spring — это Spring AOP, а нижний уровень Spring AOP — технология динамического прокси. Давайте рассмотрим динамический прокси вместе с вами:
public static void main(String[] args) {
// 目标对象
Object target ;
Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 但凡带有@Transcational注解的方法都会被拦截
// 1... 开启事务
method.invoke(target);
// 2... 提交事务
return null;
}
});
}
(Подробности см. в динамическом прокси, который я написал ранее:Объясни своей девушке, что такое прокси-режим)
Фактически обработка, выполняемая Spring, такая же, как и в приведенной выше идее.Мы можем посмотреть на класс TransactionAspectSupport.invokeWithinTransaction()
:
метод вызовавпередОткрытая транзакция, метод вызоваЗаднийсовершить транзакцию
В многопоточной среде это может произойти:После того, как метод выполнен (выполнен синхронизированный блок кода), и транзакция не была отправлена, другие потоки могут войти в метод, измененный с помощью synchronized.При повторном чтении считываются данные транзакции, которая не была отправлена, и эти данные не последние., поэтому возникает эта проблема.
3. Решить проблему
Из вышеизложенного мы видим, что проблема в том, что@Transcational
аннотации иsynchronized
используется вместе,Объем блокировки не включает всю транзакцию. Итак, мы можем сделать это:
Создайте новый класс с именем SynchronizedService и позвольте ему вызыватьaddEmployee()
метод, весь код выглядит следующим образом:
@RestController
public class EmployeeController {
@Autowired
private SynchronizedService synchronizedService ;
@RequestMapping("/add")
public void addEmployee() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();
}
}
}
// 新建的Service类
@Service
public class SynchronizedService {
@Autowired
private EmployeeService employeeService ;
// 同步
public synchronized void synchronizedAddEmployee() {
employeeService.addEmployee();
}
}
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Transactional
public void addEmployee() {
// 查出ID为8的记录,然后每次将年龄增加一
Employee employee = employeeRepository.getOne(8);
System.out.println(Thread.currentThread().getName() + employee);
Integer age = employee.getAge();
employee.setAge(age + 1);
employeeRepository.save(employee);
}
}
Мы будем синхронизировать область блокировкиВключено во всю транзакцию Spring, не будет проблем с безопасностью потоков. Во время теста мы можем обнаружить, что запущено 1000 потоков.гораздо медленнее, чем раньше, конечно, наши данные верны:
Наконец
Можно обнаружить, что, хотя нам очень удобно использовать транзакции Spring, если мы не понимаем некоторые детали транзакций Spring, часто возникают ошибки, которые трудно понять. Все еще нужно продолжать усердно работать~~~
рад вывестигалантерейные товарыОбщедоступный номер технологии Java: Java3y. В паблике более 200 статейоригинальныйТехнические статьи, обширные видеоресурсы, красивые карты мозга — идите сюдаобрати внимание нанемного!
Я думаю, что моя статья хорошо написана, пожалуйста, нажмитеотличный!