0. Новаторский код вопросов
Следующий код демонстрирует счетчик, где два потока накапливают I одновременно, каждый из которых выполняет 1000 000 раз. Результат, который мы ожидаем, определенно I = 2000000. Однако после того, как мы выполним его много раз, мы обнаружим, что значение I всегда будет менее 2000000. Это потому, что когда два потока пишут, я одновременно, результат одного из потоков будет перезаписать другой.
открытый класс AccountingSync реализует Runnable {static int i = 0; public void увеличение () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {увеличение (); }} public static void main (string [] args) бросает прерывания. Потока T1 = новый поток (AccountingSync); Потока T2 = новый поток (AccountingSync); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} Чтобы принципиально решить эту проблему, мы должны убедиться, что несколько потоков должны быть полностью синхронизированы при работе i. То есть, когда нить A записывает, я не только не может писать, но и не может его прочитать.
1. Роль синхронизированных ключевых слов
Функция синхронизированного ключевого слова на самом деле заключается в реализации синхронизации между потоками. Его задача состоит в том, чтобы заблокировать синхронизированный код, чтобы только один поток мог вводить блок синхронизации одновременно, обеспечивая тем самым безопасность между потоками. Как и в приведенном выше коде, операция i ++ может выполняться только другим потоком одновременно.
2. Использование синхронизированных ключевых слов
Укажите блокировку объекта: заблокируйте заданный объект, введите блок кода синхронизации, чтобы получить блокировку данного объекта
Непосредственно действует на методе экземпляра: он эквивалентен блокировке текущего экземпляра. Ввод в блок синхронного кода, вы должны получить блокировку текущего экземпляра (это требует, чтобы при создании потока вы должны использовать один и тот же запускаемый экземпляр)
Непосредственно действует на статических методах: это эквивалентно блокировке текущего класса. Перед входом в блок синхронного кода вы должны получить блокировку текущего класса.
2.1 Укажите объект для блокировки
Следующий код применяется синхронизированный к данному объекту. Здесь есть примечание, что заданный объект должен быть статичным, в противном случае мы не будем делиться объектом друг с другом каждый раз, когда мы новистые по потоке, поэтому значение блокировки больше не будет существовать.
открытый класс AccountingSync реализует Runnable {final Static Object object = new Object (); статический int i = 0; public void увеличение () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {synchronized (object) {увеличение (); }}} public static void main (string [] args) бросает прерывание. Потока T2 = новый поток (новый AccountingSync ()); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} 2.2 непосредственно действуйте на методе экземпляра
Синхронизированное ключевое слово действует на методе экземпляра, то есть перед вводом метода увеличения () поток должен получить блокировку текущего экземпляра. Это требует, чтобы мы использовали один и тот же экземпляр объекта запускающегося объекта при создании экземпляра потока. В противном случае блокировки потока не в том же экземпляре, поэтому невозможно поговорить о проблеме блокировки/синхронизации.
открытый класс AccountingSync реализует Runnable {static int i = 0; публичная синхронизированная пустота увеличилась () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {увеличение (); }} public static void main (string [] args) бросает прерывания. Потока T1 = новый поток (AccountingSync); Потока T2 = новый поток (AccountingSync); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }} Пожалуйста, обратите внимание на первые три строки основного метода, чтобы проиллюстрировать правильное использование ключевых слов в методе экземпляра.
2.3 непосредственно действует на статические методы
Чтобы применить синхронизированное ключевое слово к статическому методу, нет необходимости использовать два потока, чтобы указывать на тот же метод запуска, что и в приведенном выше примере. Поскольку блок метода должен запросить блокировку текущего класса, а не текущего экземпляра, потоки все еще могут быть правильно синхронизированы.
открытый класс AccountingSync реализует Runnable {static int i = 0; публичная статическая синхронизированная пустота увеличилась () {i ++; } @Override public void run () {for (int j = 0; j <1000000; j ++) {увеличение (); }} public static void main (string [] args) бросает прерывание {поток t1 = новый поток (новый AccountingSync ()); Потока T2 = новый поток (новый AccountingSync ()); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }}3. Неправильная блокировка
Из приведенного выше примера мы знаем, что если нам понадобится встречное приложение, чтобы обеспечить правильность данных, нам, естественно, необходимо заблокировать счетчик, чтобы мы могли написать следующий код:
открытый класс BadlockonInteger реализует Runnable {статическое целое число I = 0; @Override public void run () {for (int j = 0; j <1000000; j ++) {synchronized (i) {i ++; }}} public static void main (string [] args) бросает прерывания. Потока T1 = новый поток (Badlockoninteger); Потока T2 = новый поток (Badlockoninteger); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }}Когда мы запустим приведенный выше код, мы обнаружим, что выход я очень маленький. Это означает, что поток небезопасно.
Чтобы объяснить эту проблему, нам нужно начать с Integer: в Java Integer является инвариантным объектом. Как строка, как только объект создается, его нельзя изменить. Если у вас есть целое число = 1, то это всегда будет 1. Что если вы хотите этот объект = 2? Вы можете воссоздать только целое число. После каждого I ++ это эквивалентно вызову значения метода целого числа. Давайте посмотрим на исходный код значения целого числа:
public static integer value (int i) {if (i> = integerCache.low && i <= integerCache.high) return integerCache.cache [i + (-integerCache.low)]; вернуть новое целое число (i);} Integer.valueof () на самом деле является фабричным методом, который имеет тенденцию возвращать новый целочисленный объект и копировать значение I;
Поэтому мы знаем причину проблемы. Поскольку между несколькими потоками, поскольку i ++ приходит после I, я указывает на новый объект, поток может загружать разные экземпляры объекта каждый раз, когда он заблокирован. Решение очень простое. Вы можете решить его, используя один из трех методов синхронизации выше.