Как на самом деле происходит взаимодействие потоков в классе примера Counter?
Я пытаюсь изучить концепцию взаимодействия потоков и столкнулся со следующим примером в Java Tutorial Oracle:
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
В руководстве по Oracle упоминалось, что если два потока пытаются получить доступ к переменной c
, это может привести к интерференции потока, когда изменение, сделанное одним потоком, не замечено другим.
Тем не менее, он не предоставил никакого кода, чтобы фактически объяснить концепцию. Может ли кто-нибудь предоставить пример кода на основе Counter
класс, чтобы продемонстрировать, как на самом деле происходит взаимодействие потоков
3 ответа
Вместо кода я предпочитаю объяснять, что произойдет. Предположим, что 2 потока, A и B обращаются к одному и тому же объекту счетчика, A вызывает приращение и B вызывает уменьшение. Любая из этих операций состоит как минимум из 3 шагов.
- Чтение С из памяти
- Увеличение или уменьшение C
- Запишите обратно С в память.
Когда A и B пытаются увеличить и уменьшить одновременно, один поток может прочитать C из памяти (шаг 1), в то время как другой поток находится на шаге 2. в таких случаях, если начальное значение c было 5, первый поток A считал и увеличил его до 6. Затем поток B прочитал и уменьшил его до 4. Помните, что B делает эти изменения до того, как A заканчивает запись c обратно в память. Поскольку шаги перекрываются, изменения, сделанные одним потоком, не будут видны другому, в результате чего конечное значение c будет равно 6 или 4. Но на самом деле мы ожидали 5.
Это пример двух потоков, мешающих друг другу. Чтобы избежать этого, мы используем синхронизацию потоков.
Просто запустите код много раз, и в один из этих счастливых моментов вы сможете увидеть Поток Помехи в действии.
Это редко можно наблюдать в этом случае, потому что это небольшая программа и мало что происходит. Если вы сделаете несколько потоков с приращением и уменьшением, помехи между потоками будет легче наблюдать.
class Counter {
private int c = 0;
public void increment() {c++;}
public void decrement() {c--;}
public int value() {
return c;
}
public static void main(String[] args) {
Counter x = new Counter();
Runnable r1 = new Runnable() {
@Override
public void run() {
x.increment();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
x.decrement();
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println(x.c);
}
}
Изменить: я решил добавить несколько потоков случае, я не мог устоять
Редактирование 2: Это второе редактирование. В случае с несколькими потоками, поскольку это создавало проблемы, выходящие за рамки этого вопроса, я решил его удалить. Ранее я создавал массив потоков и выполнял их. Вместо этого будет лучше показать поток, который делает много приращения и другой, который делает много уменьшения.
Я использовал Thread.Sleep(). Вызывая Основной поток в спящий режим, который обязательно напечатает c после того, как оба потока завершат работу с ним.
class Counter {
private int c = 0;
public void increment() {
for (int i = 0; i < 10000; i++) {
c++;
}
}
public void decrement() {
for (int i = 0; i < 5000; i++) {
c--;
}
}
public int value() {
return c;
}
public static void main(String[] args) {
Counter x = new Counter();
Runnable r1 = new Runnable() {
@Override
public void run() {
x.increment();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
x.decrement();
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(!(t1.isAlive() && t2.isAlive()))
System.out.println(x.c);//expected answer 5000
}
}
Примечание. Синхронизированные методы увеличения / уменьшения дают правильный ответ. Попробуйте сами.
Создать несколько потоков и вызвать increment(), decrement()
а также value()
из этих тем.
Пример кода будет выглядеть так:
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
public static void main(String args[]){
Counter c = new Counter();
for ( int i=0; i<3; i++){
Thread t = new Thread(new MyRunnable(c));
t.start();
}
}
}
class MyRunnable implements Runnable{
Counter counter;
public MyRunnable(Counter c){
counter = c;
}
public void run(){
counter.increment();
System.out.println("Counter value after increment:"+counter.value()+" from thread:"+ Thread.currentThread().getName());
counter.decrement();
System.out.println("Counter value after decrement:"+counter.value()+" from thread:"+ Thread.currentThread().getName());
}
}
вывод: (Этот вывод будет меняться для каждого прогона)
Counter value after increment:1 from thread:Thread-0
Counter value after decrement:2 from thread:Thread-0
Counter value after increment:2 from thread:Thread-2
Counter value after decrement:1 from thread:Thread-2
Counter value after increment:3 from thread:Thread-1
Counter value after decrement:0 from thread:Thread-1
Теперь по выходным данным вы можете понять помехи потоков. Чтобы исправить эту проблему, вы можете использовать AtomicInteger.
Посмотрите на пост ниже для более подробной информации: