предисловие
Текст был включен в мой репозиторий GitHub, добро пожаловать, звезда:GitHub.com/bin39232820…
Лучшее время для посадки дерева было десять лет назад, затем сейчас
существуетsynchronizedВ случае блокировки метода происходит грязная запись
Tips
Вчера изначально планировалось приготовить сигарету, чашку кофе,bugЧерез день после написания вдруг наш руководитель группы сказал нам, что онлайн-среда сообщила об ошибке,
тоже появился"Сервер неисправен, обратитесь к администратору"
Разве это не авария первой степени? Хотя есть испытание снова возить ружье на фронте. Но это был модуль прямой трансляции, за который я отвечал, и куча паникующих (карта ошибок ps не была сохранена в то время)
Проанализируйте причину аварии
Потому что это ошибка (потому что я делаю этот запрос данных с помощью selectOne, поэтому будет сообщено об исключении sql) Причина в том, что я нашел это очень быстро. В базе данных есть грязные записи, как показано на рисунке:
Я отвечаю за модуль прямой трансляции.Одна из задач заключается в том, что после окончания прямой трансляции третья сторона уведомляет меня о необходимости воспроизведения прямой трансляции.
Но это воспроизведение может быть одно или несколько, но наше бизнес-требование — сохранить только одно живое воспроизведение, поэтому я сделаю следующее:
Я сделаю проверку, прежде чем делать вставки, и я также добавлю блокировку на уровне метода, и у нас есть только одна копия в строке,
На моем даже грязная надписьfuck, Я в аду
процесс решения проблем
Я собираюсь найти ответ своим непостижимым умом
Сначала я смоделировал параллельную среду:
@Test
public void TEST_TX() throws Exception {
int N = 2;
CountDownLatch latch = new CountDownLatch(N);
for (int i = 0; i < N; i++) {
Thread.sleep(100L);
new Thread(() -> {
try {
latch.await();
System.out.println("---> start " + Thread.currentThread().getName());
Thread.sleep(1000L);
CourseChapterLiveRecord courseChapterLiveRecord = new CourseChapterLiveRecord();
courseChapterLiveRecord.setCourseChapterId(9785454l);
courseChapterLiveRecord.setCreateTime(new Date());
courseChapterLiveRecord.setRecordEndTime(new Date());
courseChapterLiveRecord.setDuration("aaa");
courseChapterLiveRecord.setSiteDomain("ada");
courseChapterLiveRecord.setRecordId("aaaaaaaaa");
courseChapterLiveRecordServiceImpl.saveCourseChapterLiveRecord(courseChapterLiveRecord);
System.out.println("---> end " + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
latch.countDown();
}
}
Используйте CountDownLatch для имитации параллелизма, чтобы увидеть, есть ли проблема с данными: данные результирующей тестовой строки выглядят следующим образом:
Я действительно появился, и часть его была грязной, а часть не сработала.fuckМой ум хочет сказать это тысячу раз, как я могу это найти?
Проверил с десяток раз, а потом подумал, что должно быть что-то не так, потом успокоился, потому что набрал лог и обнаружил, что два потока действительно выполняются последовательно (скриншоты здесь не выложены)
Как мы все знаем, синхронизированный метод может гарантировать, что измененный блок кода и метод гарантируют порядок, атомарность и видимость. Так что это значит, я уверенSynchronizedОн играет свою роль: после выполнения одного потока снова будет выполняться другой поток. Внезапно вспыхнуло озарение: прочитал ли следующий поток транзакцию, которая не была отправлена в прошлый раз, когда выполнялась идемпотентная проверка?грязное чтение,грязное письмопричина
Затем я удалил аннотацию @Transactional в классе
Конечно же, я проверил это несколько раз позже, и это никогда не повторялось.
TipsОсобая благодарностьанонимНачальник указал, что я не совсем ясно изложил содержание заголовка, а окончание решения задачи было немного небрежным.
Здесь я еще раз скажу, что мой заголовок находится под управлением транзакций Spring, почему Synchronized все еще небезопасен для потоков? На самом деле я не использовал Synchronized для блокировки дел Spring
Поскольку аннотация @Transaction в моем столбце находится выше класса (то есть выше метода) объявления дел Spring, он использует идею aop
Хотя я заблокировал первый поток, когда транзакция первого потока не была зафиксирована, второй поток будет запрашивать ее, поэтому это приведет к небезопасности потока.
Решать проблему
Вариант 1 очень простой, то есть просто не открывать транзакцию, а потом не добавлять транзакцию в этот метод, т.к. Synchronized может обеспечить потокобезопасность.
Смысл этого плана в том, чтобы сказать, что не используйте @Transaction и Synchronized на одном и том же методе.Схема примера не выложена, как и моя предыдущая, просто уберите аннотацию (но только если вы уверены, что этот план делает не требует транзакций)
Вариант 2. Вызов уровня службы в этом, чтобы позволить этому методу отправить транзакцию.В этом случае добавление Synchronized также может обеспечить безопасность потоков.
Вариант 2, позвольте мне опубликовать код
@Override
public synchronized void saveCourseChapterLiveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {
saveRecord(courseChapterLiveRecord);
}
@Transactional
public void saveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {
//先查数据看是否已经存了
if (findOrder(courseChapterLiveRecord)){ return;}
int row = this.insertSelective(courseChapterLiveRecord);
if (row<1){
log.info("把录播的信息插入数据库失败 参数是->{}", JSON.toJSONString(courseChapterLiveRecord));
throw new RRException("把录播的信息插入数据库失败");
}
}
Фактически это означает обертывание транзакции в Synchronized
Сначала покритикуйте себя
Идя по дороге технологий, не думайте, что это такое, поэтому побудите себя делать что-то основательно в будущем.
благодарныйФиолетовая летящая звезда дождяЧитатель поднимает мою ошибку Причина конкретной ошибки в том, что этот объект используется при вызове метода savRecord, который на самом деле не обрабатывается АОП, то есть Transactional не вступит в силу~~~
Модифицированный код внедряет сам себя
@Override
public synchronized void saveCourseChapterLiveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {
courseChapterLiveRecordServiceImpl.saveRecord(courseChapterLiveRecord);
}
@Transactional
public void saveRecord(CourseChapterLiveRecord courseChapterLiveRecord) {
//先查数据看是否已经存了
if (findOrder(courseChapterLiveRecord)){ return;}
int row = this.insertSelective(courseChapterLiveRecord);
if (row<1){
log.info("把录播的信息插入数据库失败 参数是->{}", JSON.toJSONString(courseChapterLiveRecord));
throw new RRException("把录播的信息插入数据库失败");
}
}
Я тестировал его несколько раз в полдень, и это правда, что проблем с потокобезопасностью не будет.
Вариант 3. Также можно использовать распределенные блокировки redis, и даже множественные копии могут обеспечить потокобезопасность. Эта более поздняя статья будет писать
в заключении
В многопоточной среде может оказаться, что: метод выполняется (выполняется синхронизированный блок кода), транзакция не была отправлена, другие потоки могут войти в метод, модифицированный с помощью synchronized, и при повторном чтении происходит чтение. пока нет Данные фиксации транзакции, эти данные не самые последние, поэтому возникает эта проблема.
Ссылаясь на вывод читателя
Основная причина синхронного сбоя: потому чтоSynchronizedБлокировка — это текущий вызывающий объект метода, и Spring AOP создаст прокси-объект при обработке транзакции и откроет транзакцию до того, как прокси-объект выполнит метод, и транзакция будет зафиксирована после выполнения метода в заблокированном диапазоне. Причина сбоя блокировки синхронизации: когда A (поток) завершает выполнение метода insertSelective(), он снимает блокировку синхронизации и фиксирует транзакцию, но до того, как A (поток) зафиксирует транзакцию, B (поток) executes Метод findOrder() отправляет транзакцию вместе с A (потоком) после выполнения, и в это время возникают проблемы с безопасностью потоков.
ежедневные комплименты
Хорошо всем, вышеизложенное является полным содержанием этой статьи. Люди, которые могут видеть это здесь, всеталант.
Творить нелегко. Ваша поддержка и признание — самая большая мотивация для моего творчества. Увидимся в следующей статье.
Six Meridians Excalibur | Text [Original] Если в этом блоге есть какие-то ошибки, прошу покритиковать и посоветовать, буду очень признателен!
Ссылка на ссылку
- Синхронизированная синхронизация не удалась
- @Transactional аннотация не работает решение и принципиальный анализ
Специальная благодарность
Спасибо за ваши комментарии к статье, я буду продолжать усердно работать, чтобы добыть настоящий внутренний монолог от беленькой