0

Написал код для изучения синхронной работы потоков(в этом случае не совсем синхронной, но не суть). Класс, в котором реализуются синх. методы:

class Element{
    int element;
    Element(int n){
        element = n;
    }
    synchronized int adding(){
        element+=1;
        return element;
    }
    synchronized int subtraction(){
        element-=1;
        return element;
    }
}

Работа первого потока:

public void run(){
    while (true){
        System.out.println(element.adding());
    }
}

Работа второго потока:

public void run(){
    while (true){
        System.out.println(element.subtraction());
    }
}

До некоторого момента вывод был понятен - целые числа с разницей в 1, пока там не появилось:

...
-474
-473
-472
-553
-472
-473
...

Что это? upd. чисел с такой разницей достаточно

2 Answers2

1

Имея такую синхронизацию, выводимые числа в консоль иногда не будут отличатся на 1. Рассмотрим ваш случай:

[thread-add] -474
[thread-add] -473
[thread-add] -472
[thread-sub] -553
[thread-sub] -472
[thread-sub] -473

То есть здесь видно, что в потоке вычитания выполнилось когда-то давно вычитание (получилось число -553) но не вывелось это число, затем поток вычитания уснул, передав выполнение другому потоку, и там началось выполнение сложения до -471 (но это число не вывел), после этого уснул поток сложения, передав выполнение потоку вычитания, который ранее уснул на выполнении операции вывода, и вывел -553, затем продолжил выполнение вычитания (-471 - 1 = -472). То есть в выражении:

System.out.println(element.subtraction());

два атомарных действия: element.subtraction() и System.out.println(...), поток может уснуть после выполнения любого из них. Чтобы все-таки был вывод чисел с разницей в единицу нужно сделать такую синхронизацию:

public void run() {
    while (true) {
        synchronized (element) {
            int adding = element.adding();
            System.out.println(adding);
        }
    }
}
...
public void run() {
    while (true) {
        synchronized (element) {
            int subtraction = element.subtraction();
            System.out.println(subtraction);
        }
    }
}

То есть выполнение операций вычитания/сложения и вывода должны стать одной атомарной операцией.

nikialeksey
  • 1,515
0

UPD. Мой ответ не подходит. @nikialeksey верно написал, тут причина в неатоморности изменения числа и его вывода. Но я оставлю ответ, т.к. отсутствие volatile может приводить к похожей проблеме

Потоки в Java кэшируют данные, поэтому они могут быть неактуальными, нужно пометить element как volatile, что позволяет видеть изменения сразу, volatile int element;. Стоит отметить, в данном случае это не замена synchronized, т.к. element += 1; не атомарная операция. Т.е. тут нужно использовать как volatile так и synchronized, но на самом деле было бы куда эффективней использовать AtomicInteger, чем синхронизацию. Данный класс гарантирует атомарность операций с объектом, а изменения будут видны сразу.

final AtomicInteger element = new AtomicInteger(n);

System.out.println(element.addAndGet(1)); // поток 1
System.out.println(element.addAndGet(-1)); // поток 2
IR42
  • 4,262
  • Атомарные величины не помогут сделать вывод чисел, которые будут отличатся на 1, так как в коде каждого потока операция вывода и операция инкремента/декремента не синхронизированы. – nikialeksey Sep 30 '19 at 05:41