1.原子性、Atomic包和CAS
在java中i++不是原子性操作,因此在多線程的情況下,會存在線程安全問題。下面是對i++線程安全的測試:
static int i = 0;
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1000);
for(int j = 0; j < 1000; j ++){
new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}).start();
latch.countDown();
}
Thread.sleep(2000);
System.out.println(i);
}
得到的結果如下:
當然多次執行每次的結果都可能不一樣,也有可能結果是1000,這是因爲i++操作實際包含三個步驟:1.從內存中讀取數據到寄存器,2.寄存器中自增,3.寫入內存。與之類似,long類型的變量賦值也不是原子性操作,如long l = 1L;就不是原子性操作,因爲在賦值的時候是先寫入高32位在寫入低32位,不過加上volatile就是原子性操作,因爲volatile內部就進行了synchronized。
jdk中的Atomic包採用CAS算法保證了多線程情況下的更新操作的線程安全。CAS即Compare And Swap,是一種樂觀鎖的實現。也就是在更新的時候判斷要更新的值與當前預期的值是否一致,一致就更新並返回true不一致就返回false。
Atomic包下有AtomicBoolean、AtomicInteger、AtomicLong等類,分別用來原子性更新boolean、int和long類型的數值。這些類的使用方法都基本相同。以AtomicInteger做演示:
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomic {
static int i = 0;
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.set(i);//給atomicInteger賦值
System.out.println(atomicInteger.addAndGet(10));//加上10並返回當前的值
System.out.println(atomicInteger.getAndAdd(10));//先獲取當前值然後加10
System.out.println(atomicInteger.compareAndSet(8, 10));//比較8和當前值,如果一致就將當前值更新爲10並返回true,否則不更新並返回false
System.out.println(atomicInteger.intValue());//獲取atomicInteger中保存的int值
}
}
對於數組,有AtomicIntegerArray、AtomicBooleanArray、AtomicLongArray可以處理,對於引用類型有AtomicReference可以處理,具體用法都差不多,不一一介紹。
除了Atomic包之外,還可以
2.JMM(Java Memory Model)、可見性和有序性
一個線程對共享變量的改變,另一個線程能夠立刻看到,稱之爲可見性。在多核cpu時代,每顆cpu都有自己的緩存,線程對共享變量的操作其實是對cpu緩存的操作,cpu緩存不能立刻刷新到內存中,這就帶來了可見性的問題。
在java中,編譯器在編譯過程中進行的優化可能會使指令重排序,這稱之爲有序性。比如在new一個對象的時候,正常是先分配一塊內存,然後在內存上初始化對象,最後將內存地址賦值給變量,而編譯器優化之後可能就會是先分配一塊內存,然後將內存地址賦值給變量,最後在內存上初始化對象。
JMM(java 內存模型)允許我們通過一些方式按需的禁用cpu緩存和禁止指令重排序。這些方式包括synchronized,volatile,final和Happens-Before規則。
Happens-Before規則:
(1).程序次序規則:在一個線程內,按照程序代碼順序,書寫在前面的操作先行發生於書寫在後面的操作。準確地說,應該是控制流順序而不是程序代碼順序,因爲要考慮分支、循環等結構。
(2).管程鎖定規則:一個unlock操作先行發生於後面對同一個鎖的lock操作。這裏必須強調的是同一個鎖,而"後面"是指時間上的先後順序。
(3).volatile變量規則:對一個volatile變量的寫操作先行發生於後面對這個變量的讀操作,這裏的"後面"同樣是指時間上的先後順序。
(4).線程啓動規則:Thread對象的start()方法先行發生於此線程的每一個動作。
(5).線程終止規則:線程中的所有操作都先行發生於對此線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線程已經終止執行。
(6).線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷髮生。
(7).對象終結規則:一個對象的初始化完成(構造函數執行結束)先行發生於它的finalize()方法的開始。
(8).Happens-Before的1個特性:傳遞性。這條規則是指如果 A Happens-Before B,且 B Happens-Before C,那麼 A Happens-Before C。