2017-02-23 3 views
2

Я изучаю изменчивую переменную. Я знаю, что такое volatile, я написал образец программы для переменной volatile, но не работал, как ожидалось.Java volatile ключевое слово не работает как ожидалось

Почему конечное значение «счетчик» наступающем-то меньше, чем 2000. Я использовал неустойчивыми, следовательно, система не должна кэшировать «считать» переменной и значение всегда должно быть 2000.

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

public class Worker { 

private volatile int count = 0; 
private int limit = 10000; 

public static void main(String[] args) { 
    Worker worker = new Worker(); 
    worker.doWork(); 
} 

public void doWork() { 
    Thread thread1 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < limit; i++) { 

        count++; 

      } 
     } 
    }); 
    thread1.start(); 
    Thread thread2 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < limit; i++) { 

        count++; 

      } 
     } 
    }); 
    thread2.start(); 

    try { 
     thread1.join(); 
     thread2.join(); 
    } catch (InterruptedException ignored) {} 
    System.out.println("Count is: " + count); 
} 
} 

Благодарю вас заранее!

+1

летучие и синхронизированные не то же самое ... используйте Atomic вместо этого .... –

+2

* «Я знаю, что такое неустойчивое» * Не обижайтесь, но этот вопрос доказывает иначе. – Tom

+1

«Я использовал volatile, поэтому система не должна кэшировать переменную« count », кроме состояния гонки (очевидная проблема здесь) это совершенно неправильно. Volatile не останавливает процессор от кеширования значений в памяти - даже не сказать, что современные процессоры могут делать такие вещи, потому что это было бы бессмысленно. Volatile делает что-то совсем другое, и вы действительно не должны использовать его, не понимая, какие гарантии памяти и гарантии видимости (и после того, как вы это понимаете, вы заметите, что есть более качественные решения высокого уровня). – Voo

ответ

8

Когда вы делаете count++, это чтение, приращение, а затем запись. Каждый из них может читать каждый из двух потоков, каждый из которых выполняет свое приращение, а затем каждый записывает их, что приводит к одному приращению. Хотя ваши чтения являются атомарными, ваши записи являются атомарными, и никакие значения не кэшируются, этого недостаточно. Вам нужно больше, чем это - вам нужна операция чтения атома атома, и volatile не предоставляет этого.

+0

хорошо информация !! –

2

count++ в основном это:

// 1. read/load count 
// 2. increment count 
// 3. store count 
count = count + 1; 

индивидуально операция first и third атомарные. Все 3 из них together не являются атомарными.

1

i++ is not atomic in Java. Таким образом, два потока могут одновременно считываться, оба вычисляют +1 на одинаковое число, и оба сохраняют один и тот же результат.

Компиляция это с помощью javac inc.java:

public class inc { 
    static int i = 0; 
    public static void main(String[] args) { 
     i++; 
    } 
} 

Прочитайте байткод, используя javap -c inc. Я обрезается это вниз, чтобы просто показать функцию main():

public class inc { 
    static int i; 

    public static void main(java.lang.String[]); 
    Code: 
     0: getstatic  #2     // Field i:I 
     3: iconst_1 
     4: iadd 
     5: putstatic  #2     // Field i:I 
     8: return 
} 

Мы видим, что приращение (статического межд) реализуется с помощью: getstatic, iconst_1, iadd и putstatic.

Поскольку это сделано с четырьмя инструкциями и без замков, не может быть ожиданий атомарности. Также стоит отметить, что даже если бы это было сделано с 1 инструкцией, мы можем быть не повезло (цитата из пользователя «Горячие лижет» комментарий в this thread):

Даже на оборудовании, который реализует «приращение местоположения хранения» инструкции , нет никакой гарантии, что это поточно-безопасный. Просто потому, что операция может быть представлена ​​в виде одного оператора, который ничего не говорит о безопасности потоков.


Если вы на самом деле хотели решить эту проблему, вы можете использовать AtomicInteger, который имеет гарантию атомарность:

final AtomicInteger myCoolInt = new AtomicInteger(0); 
myCoolInt.incrementAndGet(1); 
1

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

От tutorial:

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

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

При использовании volatile (как это объясняется другими) этот цикл не является атомарным, как с помощью этого ключевого слова не гарантирует, что не будет никакой другой записью на переменном на других потоках между ГЕТЕ и шаги приращения по этой теме.

Для подсчета атомов, а не для ключевого слова synchronized, вы можете использовать, например. AtomicInteger:

public class Worker { 
    private AtomicInteger count = new AtomicInteger(0); 
    private int limit = 10000; 

    public static void main(String[] args) { 
     Worker worker = new Worker(); 
     worker.doWork(); 
    } 

    public void doWork() { 
     Thread thread1 = new Thread(new Runnable() { 
      public void run() { 
       for (int i = 0; i < limit; i++) 
        count.getAndIncrement(); 
      } 
     }); 
     thread1.start(); 
     Thread thread2 = new Thread(new Runnable() { 
      public void run() { 
       for (int i = 0; i < limit; i++) 
        count.getAndIncrement(); 
      } 
     }); 

     thread2.start(); 

     try { 
      thread1.join(); 
      thread2.join(); 
     } catch (InterruptedException ignored) { 
     } 
     System.out.println("Count is: " + count); 
    } 
} 

Здесь getAndIncrement() обеспечивает атомное чтения инкремент-множественный цикл.

1

Память Видимость и атомарность - две разные, но общие проблемы в многопоточности. Когда вы используете синхронизированное ключевое слово, оно обеспечивает как приобретение блокировок. В то время как volatile только решает проблему видимости памяти. В своей книге Concurrency in practice, Brain Goetz объясняет, когда вы должны использовать volatile.

  1. Пишет переменной не зависит от его текущего значения, или вы может гарантировать, что только один поток постоянно обновляет значение;
  2. Переменная не участвует в инвариантах с другим состоянием переменных ;
  3. Блокировка не требуется по какой-либо другой причине, пока переменная имеет значение .

Ну, в вашем случае посмотрите на счет операции ++, который не является атомарным.