文章目錄
- 比較
- volatile的非原子特性
- 使用原子類來進行++操作
- 原子類也不一定是執行緒安全的
- 關鍵字synchronized代碼塊有volatile同步變數的功能
比較
- 執行緒安全主要就是原子性和可見性倆個方面, Java中的同步機制就是圍繞這倆個方面來確保執行緒的安全.
- 通過使用volatile關鍵字,是強制的從公共記憶體中讀取變數的值, 是執行緒同步的輕量級實作, 所以volatile性能肯定比synchronized要好,并且 volatile只能修飾于變數,而synchronized可以修飾方法,以及代碼塊,
- 隨著JDK新版本的發布,synchronized關鍵字在執行效率上得到很大提升,在開發中使用synchronized關鍵字的比率還是比較大的,
- 多執行緒訪問volatile不會發生阻塞,而synchronized會出現阻塞,
- volatile能保證資料的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有記憶體和公共記憶體中的資料做同步,

volatile的非原子特性
- 服務類
public class ServiceAdd {
volatile public int count = 0;
public void add() {
this.count++;
}
}
- 運行類
public class Run {
public static void main(String[] args) throws InterruptedException {
ServiceAdd serviceAdd = new ServiceAdd();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
serviceAdd.add();
}
}
};
}
for (int i = 0; i < 100; i++) {
threads[i].start();
}
for (Thread t : threads
) {
t.join();
}
System.out.println(serviceAdd.count);
}
}
- 運行結果

關鍵字volatile提示執行緒每次從共享記憶體中讀取變數,而不是從私有記憶體中讀取,這樣就保證了同步資料的可見性,但在這里需要注意的是:如果修改實體變數中的資料,比如i++,也就是i=i+1,則這樣
的操作其實并不是一個原子操作,也就是非執行緒安全的,運算式i++的操作步驟分解如下:
1)從記憶體中取出i的值;
2)計算i的值;
3)將i的值寫到記憶體中,
假如在第2步計算值的時候,另外一個執行緒也修改i的值,那么這個時候就會出現臟資料,解決的辦法其實就是使用synchronized 關鍵字,所以說volatile本身并不處理資料的原子性,而是強制對資料的讀寫及時影響到主記憶體的,
用圖來演示一下使用關鍵字volatile時出現非執行緒安全的原因,變數在記憶體中作業的過

- 在多執行緒環境中,use和asign是多次出現的,但這一操作并不是原子性,也就是在read和 load之后,如果主記憶體count變數發生修改之后,執行緒作業記憶體中的值由于已經加載,不會產生對應的變化,也就是私有記憶體和公共記憶體中的變數不同步,所以計算出來的結果會和預期不一樣,也就出現了非執行緒安全問題,
- 對于用volatile修飾的變數,JVM虛擬機只是保證從主記憶體加載到執行緒作業記憶體的值是最新的,例如執行緒1和執行緒2在進行read和 load的操作中,發現主記憶體中count的值都是5,那么都會加載這個最新的值,也就是說,volatile關鍵字解決的是變數讀時的可見性問題,但無法保證原子性,對于多個執行緒訪問同一個實體變數還是需要加鎖同步,
使用原子類來進行++操作
原子操作是不能分割的整體,沒有其他執行緒能夠中斷或檢查正在原子操作中的變數,一個原子(atomic)型別就是一個原子操作可用的型別,它可以在沒有鎖的情況下做到執行緒安全( thread-safe),
- 服務類
public class ServiceAtomic {
public AtomicInteger count = new AtomicInteger(0);
public void add() {
count.incrementAndGet();
}
}
- 運行類
public class Run2 {
public static void main(String[] args) throws InterruptedException {
ServiceAtomic atomic = new ServiceAtomic();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
atomic.add();
}
}
};
}
for (int i = 0; i < 100; i++) {
threads[i].start();
}
for (Thread t : threads
) {
t.join();
}
System.out.println(atomic.count);
}
}
- 運行結果

原子類也不一定是執行緒安全的
- 因為原子類只是單純保證了我運行其的某個方法是原子的, 但是在我們自己寫的代碼中, 并沒有保證原子性, 所以即使我們使用了原子類但是在我們自己寫的代碼面前再加上一點邏輯的話, 就還會有執行緒安全問題
- 服務類
public class ServiceAdd2 {
public AtomicInteger count = new AtomicInteger(0);
public void addOne() {
System.out.println("執行加一: " + count.addAndGet(1));
}
public void addHundred() {
System.out.println("執行加一百: " + count.addAndGet(100));
}
}
- 運行類
public class Run3 {
public static void main(String[] args) throws InterruptedException {
ServiceAdd2 serviceAdd2 = new ServiceAdd2();
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
threads[i] = new Thread() {
@Override
public void run() {
serviceAdd2.addOne();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
serviceAdd2.addHundred();
}
};
}
for (Thread t : threads
) {
t.start();
}
for (Thread t : threads
) {
t.join();
}
}
}
- 運行結果

想要改變上面的問題, 只需要給服務類加上synchronized修飾那倆個方法即可
關鍵字synchronized代碼塊有volatile同步變數的功能
- 關鍵字synchronized可以保證在同一時刻,只有一個執行緒可以執行某一個方法或某一個代碼塊,它包含兩個特征:互斥性和可見性,同步synchronized不僅可以解決一個執行緒看到物件處于不一致的狀態,還可以保證進入同步方法或者同步代碼塊的每個執行緒,都看到由同一個鎖保護之前所有的修改效果,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/160257.html
標籤:其他
上一篇:攻防世界 Pwn 新手
