2016-05-28 2 views
1

Мне интересно, почему результат не 400 000. Есть две темы, почему он блокируется?Проблема с потоками Java?

class IntCell { 
    private int n = 0; 
    public int getN() {return n;} 
    public void setN(int n) {this.n = n;} 
} 
class Count extends Thread { 
    private static IntCell n = new IntCell(); 
    @Override public void run() { 
     int temp; 
     for (int i = 0; i < 200000; i++) { 
      temp = n.getN(); 
      n.setN(temp + 1); 
     } 
    } 
    public static void main(String[] args) { 
     Count p = new Count(); 
     Count q = new Count(); 
     p.start(); 
     q.start(); 
     try { p.join(); q.join(); } 
     catch (InterruptedException e) { } 
     System.out.println("The value of n is " + n.getN()); 
    } 
} 

Почему существует проблема с этим?

+3

Что вы подразумеваете под словом «зачем оно блокируется»? Что касается того, что не так - представьте, если оба потока выбрали конкретное значение (скажем, 100), то оба прироста (до 101), а затем сохраните это значение. Два приращения произошли, но результат равен 101 вместо 102 ... –

+0

Возможно, вы захотите добавить полученные результаты. FYI: это фундаментальная вещь, которую вам нужно понимать с помощью потоков и одновременных обновлений значений. –

+0

Я предлагаю вам пойти и прочитать, как работает многопоточность. Ваш вопрос подразумевает серьезное заблуждение. Многопоточность настолько трудно понять, что вам нужно иметь хорошее понимание, прежде чем начать. – davmac

ответ

2

Когда два потока доступ к одному объекту, в то же время, что они мешают друг другу, и результат не является детерминированным. Например, представьте, что p считывает значение n и получает, скажем, 0, то q считывает одно и то же значение и также получает 0, затем p устанавливает значение в 1 и q также устанавливает его в 1 (поскольку он все еще считает, что он значение 0). Теперь значение n увеличивается на 1, хотя оба счетчика «увеличивают» его один раз. Вам нужно использовать блок synchronized, чтобы убедиться, что счетчики не будут мешать друг другу. См. https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html для получения дополнительной информации.

6

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

  1. Получить предыдущее значение
  2. Добавьте один к этому значению
  3. установить новое значение

Это 3 операции, которые не выполняются атомарно, вы должны либо использовать блок synchronized, либо использовать вместо него AtomicInteger.

С synchronized блока было бы что-то вроде:

synchronized (n) { 
    temp = n.getN(); 
    n.setN(temp + 1); 
} 

с AtomicInteger вам нужно будет переписать свой код в следующем:

class IntCell { 
    private final AtomicInteger n = new AtomicInteger(); 
    public int getN() {return n.get();} 
    public void incrementN(int n) {this.n.addAndGet(n);} 
} 

for (int i = 0; i < 200000; i++) { 
    n.incrementN(1); 
} 

Подход с AtomicInteger не является блокировка так он будет быстрее

2

Проблема в том, что вы разрешаете условия гонки. Рассмотрим блок внутри цикла:

temp = n.getN(); 
n.setN(temp + 1); 

код переключения контекста между временем получения текущего N и к тому времени, когда вы увеличить его, делая вас установить «старое» значение. Один из способов обойти это, чтобы обеспечить внутреннюю часть трассы контура в синхронизированном блоке:

for (int i = 0; i < 200000; i++) { 
    synchronized (n) {/Here! 
     temp = n.getN(); 
     n.setN(temp + 1); 
    } 
}