java併發編程學習(六)

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緩存和禁止指令重排序。這些方式包括synchronizedvolatilefinalHappens-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。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章