2016-02-28 4 views
1

Я пытаюсь узнать концепцию потоков вмешательства и столкнулся следующий пример в Java Tutorial Oracle:Как потоковая интерференция действительно происходит в классе класса Counter?

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 называет декремент. Любая из этих операций состоит не менее чем из 3 шагов.

  1. Чтение С из памяти
  2. увеличить или уменьшить С
  3. Написать назад C памяти.

Когда А и В, чтобы попытаться увеличивать или уменьшать в то же время, одна нить может прочитать C из памяти (этап 1), в то время как другой поток находится на стадии 2 . в таких случаях, если начальное значение c было 5, первая резьба A прочитала и увеличила ее до 6. Затем Thread B считывает и уменьшает ее до 4. Помните, что B делает эти изменения до того, как A заканчивает запись c назад память. Поскольку этапы перекрываются, изменения, сделанные одним потоком, не будут видны другим, в результате чего конечное значение c будет либо 6, либо 4. Но на самом деле то, что мы ожидали, было .

Это пример двух потоков, мешающих друг другу. Чтобы этого избежать, мы используем синхронизацию потоков.

1

Просто запустите код много раз, и в одно из этих счастливых времен вы сможете увидеть вмешательство потока в действии.

В этом случае очень редко наблюдать, потому что его небольшая программа и мало что происходит. Если вы делаете несколько приращений и уменьшаете потоки, то помехи по резьбе будут легче наблюдать.

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); 

} 

} 

Edit: Я решил добавить несколько случая нити, я не мог сопротивляться

Edit 2: Это второй edit.The множественного случая нити так, что создавало проблему выходит за рамки этого вопрос Я решил удалить его. Очевидно, я делал массив потоков и запускал их. Вместо этого будет лучше показать поток, который делает много приращений, а другой, который делает много декрементов.

Я использовал 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 
} 
} 

Примечание: Синхронные методы приращения/уменьшения дают правильный ответ. Попробуйте сами.

1

Создайте несколько тем и позвоните 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.

Посмотрите ниже пост для более подробной информации:

Why is synchronized not working properly?