Почему синхронизация не работает должным образом?

Вот мой код:

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();
}
Другие вопросы по тегам