1. 程式人生 > >JAVA多執行緒的三大特性

JAVA多執行緒的三大特性

多執行緒的三大特性

**在瞭解三大特性前,我們先複習下之前學過的多執行緒的生命週期

多執行緒的生命週期(執行緒狀態)

1,新建狀態(New)
新建立了一個執行緒物件。

2,就緒狀態(Runnable)
執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,變得可執行,等待獲取CPU的使用權。

3,執行狀態(Running)
就緒狀態的執行緒獲取了CPU,執行程式程式碼。

4,阻塞狀態(Blocked)
阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。
阻塞的情況分三種:
(一),等待阻塞:執行的執行緒執行wait()方法,JVM會把該執行緒放入等待池中。
(二),同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入鎖池中。
(三)、其他阻塞:執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。

5,死亡狀態(Dead)
執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

(畫圖說明)

Java的記憶體模型(JMM)

全稱:Java Memory Model

java記憶體模型
1,java中所有的變數都儲存在主記憶體中(main memory),每條執行緒還有自己的工作記憶體(Working Memory),
2,執行緒的工作記憶體中儲存了主記憶體的副本拷貝,執行緒對所有變數的操作都必須在工作記憶體中進行而不能操作主記憶體的變數。
3,不同的執行緒不能訪問別的執行緒工作記憶體中的變數,執行緒之間的變數值傳遞必須要通過主記憶體來完成,
4,執行緒、主記憶體、工作記憶體三者的互動關係畫圖演示
JMM規範定義了工作記憶體和主記憶體之間的訪問細節,通過保障原子性,可見性,有序性實現執行緒安全。

1),原子性

1,什麼時原子性?
即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

2,舉個栗子
一,銀行賬戶轉賬問題
二,操作資料 :i=i+1;
這些不具備原子性的問題,則多執行緒執行肯定會出問題,所以也需要我們使用鎖和lock這些東西來確保這個特性了。

2),可見性

1,什麼是可見性?
當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。

2,Java記憶體模型理解可見性
java記憶體模型是通過,A執行緒對變數在修改後的新值同步回主記憶體中,這時B執行緒讀取的變數,會是從主記憶體中讀取A執行緒修改後的變數值。
這種依賴 主記憶體作為傳遞媒介的方式來實現可見性的。
通過volatile保證了執行緒之間的可見性。同樣還有synchronized 和final 來實現執行緒可見性。

3,舉個栗子

//執行緒1執行的程式碼
int i = 0;
i = 10;
 
//執行緒2執行的程式碼
j = i;

4,volatiles保障可見性

1,volatiles
它是依賴CPU提供的特殊指令記憶體屏障指令來控制可見性,被Volatile修飾的成員變數在被執行緒訪問時在讀操作前會強行插入一條記憶體屏障讀指令強行從主存中讀取(讓快取記憶體中的資料失效,重新從主記憶體載入資料),變數在被執行緒修改時會在寫指令之後插入寫屏障,讓寫入快取的最新資料寫回到主記憶體。
Volatile只是保證變數可見性,並不能確保原子性,不能確保原子性,是因為如果A執行緒和B執行緒同時讀取到變數a值,A執行緒修改a後將值刷到主存、同時B執行緒也修改了a的值並刷到主存,這時候B執行緒就覆蓋了A執行緒修改操作。

	(程式碼舉慄說明)

package com.thread.test;

public class VolatileTest {
	public volatile int inc = 0;

	public void increase() {
		inc++;
	}

	public static void main(String[] args) {
		final VolatileTest test = new VolatileTest();
		for (int i = 0; i < 1000; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 1000; j++)
						test.increase();
				};
			}.start();
		}

		while (Thread.activeCount() > 1)
			// 保證前面的執行緒都執行完
			Thread.yield();
		System.out.println(test.inc);
	}
}

2,Synchronized

	Synchronized是通過對執行緒加鎖(獨佔鎖)控制執行緒同步,被Synchronized修飾的記憶體只允許一個執行緒訪問 .			

	(程式碼舉慄說明)

package com.thread.test;

public class SynchronizedTest {
    public  int inc = 0;
    
    public synchronized void increase() {
        inc++;
    }
    
    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
        for(int i=0;i<1000;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
        
        while(Thread.activeCount()>1)  //保證前面的執行緒都執行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

3,小結
volatile關鍵字和synchronized 都可以來保證可見性
但是 volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。 也就是volatile在某些情況下並不能保障執行緒安全。

3),有序性

1,什麼是有序性?
即程式執行的順序按照程式碼的先後順序執行

2,舉個簡單的栗子,看下面這段程式碼:

int i = 0;              
boolean flag = false;
i = 1;                //語句1  
flag = true;          //語句2

1,重排序:
指令重排序,一般來說,處理器為了提高程式執行效率,可能會對輸入程式碼進行優化,它不保證程式中各個語句的執行先後順序同程式碼中的順序一致,但是它會保證程式最終執行結果和程式碼順序執行的結果是一致的。

2,再看一個栗子:

int a = 10;    //語句1
int r = 2;    //語句2
a = a + 3;    //語句3
r = a*a;     //語句4

		所以從上來看重排序是不會影響單執行緒的,但是對於多執行緒呢?

3,再來一個栗子:

x = 2;        //語句1
y = 0;        //語句2
x = 4;         //語句3
y = -1;       //語句4

//flag為volatile變數
x = 2;        //語句1
y = 0;        //語句2
flag = true;  //語句3
x = 4;         //語句4
y = -1;       //語句5

3,volatile關鍵字禁止指令重排序

1)
當程式執行到volatile變數的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;

2)
在進行指令優化時,不能將在對volatile變數訪問的語句放在其後面執行,也不能把volatile變數後面的語句放到其前面執行。

4),總結
要想併發程式正確地執行,必須要保證原子性、可見性以及有序性。只要有一個沒有被保證,就有可能會導致程式執行不正確。