Почему синхронизация не работает должным образом?
Вот мой код:
private int count = 0;
public synchronized void increment() {
count++;
}
public void doWork() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
increment();
System.out.println(count+" "+Thread.currentThread().getName());
}}});
Thread t2 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
increment();
System.out.println(count+" "+Thread.currentThread().getName());
}}});
t1.start();
t2.start();
}
Вот мой вывод:
2 Thread-1
2 Thread-0
3 Thread-1
5 Thread-1
6 Thread-1
4 Thread-0
8 Thread-0
9 Thread-0
7 Thread-1
10 Thread-0
Я понимаю, что increment
является synchronized
, Итак, сначала следует increment
один номер, а затем отпустите lock
а затем дать lock
в потоке t1
или же t2
, Итак, это должно increment
по одному номеру за раз, верно?
Но почему мой код incrementing
два или три числа одновременно? Я что-то не так делаю (я новичок)?
5 ответов
В то время как count++;
действительно синхронизируется System.out.println(count+" "+Thread.currentThread().getName());
нет, но это доступ к count
переменная.
Даже если вы синхронизируете доступ, это не поможет, потому что следующий сценарий все еще возможен:
- Шаг 1
- Шаг 2
- Нить 1 печатная стоимость
2
- Значение печати нити 2
2
Чтобы решить эту проблему, вы должны увеличить и распечатать в том же синхронизированном разделе. Например, вы можете положить System.out.println(count+" "+Thread.currentThread().getName());
в increment
метод.
increment
метод может быть запущен в другом потоке после increment
метод возвращается, но до count
извлекается для объединения
count+" "+Thread.currentThread().getName()
Например, вы можете исправить это, изменив и получив count
в одном синхронизированном блоке:
public synchronized int incrementAndGet() {
count++;
return count; // read access synchronized
}
for (int i = 0; i < 5; i++) {
System.out.println(incrementAndGet()+" "+Thread.currentThread().getName());
}
Или используйте класс в стандартной библиотеке, специально предназначенной для этой цели:
private final AtomicInteger counter = new AtomicInteger(0);
public void doWork() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(counter.incrementAndGet() + " " + Thread.currentThread().getName());
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(counter.incrementAndGet() + " " + Thread.currentThread().getName());
}
}
});
t1.start();
t2.start();
}
Конечно, это не обязательно приводит к тому, что числа от 1 до 10 печатаются по порядку, только то, что ни одно число не получается более одного раза. Может произойти следующий вывод:
2 Thread-0
3 Thread-0
4 Thread-0
1 Thread-1
5 Thread-0
6 Thread-1
7 Thread-0
8 Thread-1
9 Thread-1
10 Thread-1
Что на самом деле происходит, так это то, что ваши потоки делают моментальный снимок (может быть, здесь лучше другое слово) текущее значение переменной count
и отобразить это. Вы можете думать об этом как о синем ведре с номером ноль и обоими Threads
получают одинаковое ведро в том же цвете и количестве. Теперь они работают индивидуально с этими ведрами.
Если вы хотите, чтобы они работали в одном ведре, вы должны сделать их атомарными, например, с AtomicInteger
или же volatile
или любой другой инструмент из параллельного пакета Java.
Одно альтернативное решение без использования synchronized
,
Поскольку ваш вариант использования прост (просто добавьте счетчик и напечатайте значение, AtomicInteger - лучший выбор).
import java.util.concurrent.atomic.AtomicInteger;
public class TestCounter{
private AtomicInteger count = new AtomicInteger(0);
public void doWork() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(""+Thread.currentThread().getName()+":"+count.incrementAndGet());
}}});
Thread t2 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(""+Thread.currentThread().getName()+":"+count.incrementAndGet());
}}});
t1.start();
t2.start();
}
public static void main(String args[]) throws Exception{
TestCounter tc = new TestCounter();
tc.doWork();
}
}
выход:
Thread-0:1
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-1:2
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10
Обратитесь к ответу @fabian, чтобы узнать, почему эти цифры не напечатаны в последовательности.
Если вы ожидаете последовательность в последовательности чисел в порядке возрастания от 1 до 10, потоки не требуются.
Решение 1: дано Фабианом. Чтобы дать одну функцию incrementAndGet()
,
Решение 2: А synchronized
блок вместо synchronized
метод (если возможно):
Полный код будет выглядеть так:
private int count = 0;
private Object dummyObject = new Object();
public void increment() {
count++;
}
public int getCount() {
return count;
}
public void doWork() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
synchronized (dummyObject) {
increment();
System.out.println(count + " " + Thread.currentThread().getName());
}
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
synchronized (dummyObject) {
increment();
System.out.println(count + " " + Thread.currentThread().getName());
}
}
}
});
t1.start();
t2.start();
}