Оба потока имеют доступ к вашей переменной.
Явление, которое вы видите, называется потоком голода. После ввода защищенной части вашего кода (извините, что я пропустил это ранее), другие потоки должны будут блокироваться до тех пор, пока не будет выполнена нить, удерживающая монитор (т. Е. Когда выдается монитор). Хотя можно ожидать, что текущая нить пройдет монитор до следующего потока, ожидающего очереди, для синхронизированных блоков java не гарантирует никакой политики справедливости или порядка, к которой поток затем получает монитор. Это вполне возможно (и даже возможно) для потока, который выпускает и пытается повторно захватить монитор, чтобы схватить его за другой поток, который ждал некоторое время.
От Oracle:
Голодание описывает ситуацию, когда поток не в состоянии получить постоянный доступ к общим ресурсам и не в состоянии добиться прогресса. Это происходит, когда общие ресурсы становятся недоступными в течение длительного времени «жадными» потоками. Например, предположим, что объект предоставляет синхронизированный метод, который часто требует много времени для возврата. Если один поток часто вызывает этот метод, часто блокируются другие потоки, которые также нуждаются в частом синхронном доступе к одному и тому же объекту.
Хотя ваши потоки являются примерами «жадных» нитей (поскольку они многократно выпускают и снова захватывают монитор), сначала начинается нить-0, поэтому голодает нить-1.
Решение состоит в том, чтобы использовать параллельный метод синхронизации, который поддерживает справедливость (например ReentrantLock), как показано ниже:
public class ThreadsExample implements Runnable {
static int counter = 1; // a global counter
static ReentrantLock counterLock = new ReentrantLock(true); // enable fairness policy
static void incrementCounter(){
counterLock.lock();
// Always good practice to enclose locks in a try-finally block
try{
System.out.println(Thread.currentThread().getName() + ": " + counter);
counter++;
}finally{
counterLock.unlock();
}
}
@Override
public void run() {
while(counter<1000){
incrementCounter();
}
}
public static void main(String[] args) {
ThreadsExample te = new ThreadsExample();
Thread thread1 = new Thread(te);
Thread thread2 = new Thread(te);
thread1.start();
thread2.start();
}
}
примечание удаление synchronized
ключевого слова в пользу ReentrantLock в рамках метода. Такая система, с политикой справедливости, позволяет долго ждать потоков, чтобы выполнить, устраняя голод.
Почему, по-вашему, цикл while означает это? – RealSkeptic
Ничего не нужно «исправлять». Переключение между потоками является дорогостоящим, и планировщик ** не ** чередуется, чтобы потоки выполнялись для одиночных вызовов 'incrementCounter'. Увеличьте переменную цикла от 1000 до, скажем, 1000000, тогда вы увидите, что потоки действительно * делают * изменяются между ними. – Marco13
@ Marco13 Хотя это примитивный пример, и нет никакого смысла переключаться между потоками, существуют случаи, когда короткие, но важные задачи, подобные этому, не выполняются из-за того, что один поток забивает монитор, вызывая головокружение нити, что потенциально останавливает прогресс. В этом случае, безусловно, есть что-то, что можно «исправить», реализуя политику справедливости. – initramfs