В чем разница между энергозависимым и синхронизированным?
Я пытаюсь увидеть как volatile
работает здесь. Если я заявляю cc
как volatile
Я получаю вывод ниже. Я знаю, что результат выполнения потока меняется время от времени, но я где-то читал, что volatile
такой же как synchronized
, так почему я получаю этот вывод? И если я использую два экземпляра Thread1
это имеет значение?
2Thread-0 2Thread-1 4Thread-1 3Thread-0 5Thread-1 6Thread-0 7Thread-1 8Thread-0 9Thread-1 10Thread-0 11Thread-1 12Thread-0
public class Volexample {
int cc=0;
public static void main(String[] args) {
Volexample ve=new Volexample();
CountClass count =ve.new CountClass();
Thread1 t1=ve.new Thread1(count);
Thread2 t2=ve.new Thread2(count);
t1.start();
t2.start();
}
class Thread1 extends Thread{
CountClass count =new CountClass();
Thread1(CountClass count ){
this.count=count;
}
@Override
public void run() {
/*for(int i=0;i<=5;i++)
count.countUp();*/
for(int i=0;i<=5;i++){
cc++;
System.out.println(cc + Thread.currentThread().getName());
}
}
}
class Thread2 extends Thread {
CountClass count =new CountClass();
Thread2(CountClass count ){
this.count=count;
}
@Override
public void run(){
/*for(int i=0;i<=5;i++)
count.countUp();*/
for(int i=0;i<=5;i++){
cc++;
System.out.println(cc + Thread.currentThread().getName());
}
}
}
class CountClass{
volatile int count=0;
void countUp(){
count++;
System.out.println(count + Thread.currentThread().getName());
}
}
}
3 ответа
В Java семантика volatile
Ключевое слово очень хорошо определены. Они гарантируют, что другие потоки увидят последние изменения в переменной. Но они не делают операции чтения-изменения-записи атомарными.
Так что если i
является volatile
и вы делаете i++
, вы гарантированно прочитали последние значения i
и вам гарантировано, что другие темы увидят вашу запись в i
немедленно, но вам не гарантируется, что два потока не будут чередовать свои операции чтения / изменения / записи, чтобы два приращения имели эффект одного приращения.
предполагать i
является переменным целым числом, значение которого было инициализировано равным нулю, кроме записи еще не было записей, и два потока делают i++;
может произойти следующее:
- Первый поток читает ноль, последнее значение
i
, - Второй поток читает ноль, а также последнее значение
i
, - Первый поток увеличивает ноль, который он прочитал, получая единицу.
- Второй поток увеличивает ноль, который он прочитал, также получая единицу.
- Первый поток записывает тот, на который рассчитал
i
, - Второй поток записывает тот, для которого он рассчитан
i
, - Последнее значение написано
i
один, так что любой поток, который обращаетсяi
сейчас увидим.
Обратите внимание, что приращение было потеряно, хотя каждый поток всегда считывал последнее значение, записанное любым другим потоком. volatile
Ключевое слово дает видимость, а не атомарность.
Ты можешь использовать synchronized
формировать сложные атомные операции. Если вам просто нужны простые, вы можете использовать различные Atomic*
классы, которые предоставляет Java.
Вариант использования для использования volatile будет чтение / запись из памяти, которая сопоставлена с регистрами устройства, например, на микроконтроллере, где что-то кроме CPU будет считывать / записывать значения в этот адрес "памяти" и, таким образом, компилятор не следует оптимизировать эту переменную.
Ява volatile
Ключевое слово используется для пометки переменной Java как "хранящейся в основной памяти". Это означает, что каждое чтение энергозависимой переменной будет считываться из основной памяти компьютера, а не из кеша, и что каждая запись в энергозависимую переменную будет записываться в основную память, а не только в кеш.
Это гарантирует, что вы получаете доступ к новейшему значению этой переменной.
PS Используйте большие петли, чтобы заметить ошибки. Например, попробуйте повторить 10e9 раз.