概述
volatile是Java提供的輕量級的同步機制,保證了可見性,不保證原子性。
了解volatile工作機制,首先要對Java內存模型(JMM)有初步的認識:
- 每個線程創建時,JVM會為其創建一份私有的工作內存(棧空間),不同線程的工作內存之間不能直接互相訪問
- JMM規定所有的變量都存在主內存,主內存是共享內存區域,所有線程都可以訪問
- 線程對變量進行讀寫,會從主內存拷貝一份副本到自己的工作內存,操作完畢后刷新到主內存。所以,線程間的通信要通過主內存來實現。
- volatile的作用是:線程對副本變量進行修改后,其他線程能夠立刻同步刷新最新的數值。這個就是可見性。
可見性驗證
如下一段代碼,number字段沒有用volatile修飾。
- 創建一個子線程
- 子線程sleep 3s(目的是讓主線程先加載number=0的變量)
- 子線程把number改成100。
- 這時主線程的number仍然為0,不會同步成100
public class 可見性 {
static class MyTest {
public int number = 0;
public void changeNumber(){
number = 100;
}
}
public static void main(String[] args) throws InterruptedException{
MyTest myTest = new MyTest();
new Thread(() -> {
System.out.println(String.format("線程%s開始執行", Thread.currentThread().getName()));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myTest.changeNumber();
System.out.println(String.format("線程%s的number:%d", Thread.currentThread().getName(), myTest.number));
}, "NewThread").start();
while (myTest.number == 0){
}
System.out.println("執行完畢");
}
}
執行結果如下,一直卡在while循環,不會輸出最后一條“執行完畢”
還是上面的代碼,給number變量加上volatile關鍵字
static class MyTest {
public volatile int number = 0;
public void changeNumber(){
number = 100;
}
}
重新執行一下,結果變了,主線程能夠及時同步number值的變動
原子性驗證
看下面一段代碼,number變量加了volatile修飾。創建了10個子線程,每個線程循環1000次執行number++。
public class 原子性1
{
static class MyTest {
public volatile int number = 0;
public void incr(){
number++;
}
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
for (int i = 1; i <= 10; i++){
new Thread(() -> {
for (int j = 1; j <= 1000; j++){
myTest.incr();
}
}, "Thread"+String.valueOf(i)).start();
}
//等線程執行結束了,輸出number值
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println("當前number:" + myTest.number);
}
}
按理說number最終應該是10000,但是這邊執行后,結果如下:
原子性問題解決
方法一:使用 synchronized 關鍵字
//給函數增加synchronized修飾,相當於加鎖了
public synchronized void incr(){
number++;
}
結果如下:
方法二:使用AtomicInteger
public class 原子性2
{
static class MyTest {
public volatile AtomicInteger number = new AtomicInteger();
public void incr(){
number.getAndIncrement();
}
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
for (int i = 1; i <= 10; i++){
new Thread(() -> {
for (int j = 1; j <= 1000; j++){
myTest.incr();
}
}, "Thread"+String.valueOf(i)).start();
}
//等線程執行結束了,輸出number值
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println("當前number:" + myTest.number);
}
}
輸出結果一樣是10000,這里就不再貼圖了