1. 程式人生 > >執行緒(一)

執行緒(一)

執行緒 Thread

1)應用程式以程序為單位執行,一個程序之內可以分為一到多個執行緒
2)作業系統有個元件叫任務排程器,將cpu的時間分給不同程式使用,微觀序列(單核),巨集觀並行(多核)
3)可以使用jconsle 來檢視某個java程序中執行緒的執行情況,包括死鎖等

 

好處:
	1. 多程序,多執行緒可以讓程式不被阻塞.
	2. 充分利用多核cpu的優勢,提高執行效率

課程要求
1.掌握建立執行緒的方法和常用的sleep,join方法等
2.掌握synchronized解決併發問題,熟悉使用wait,notify方法
3.瞭解死鎖,執行緒的五種狀態

 

 

多執行緒

Thread類

建立執行緒

方法一:

 

Thread t = new Thread(){
        public void run(){
        //執行程式碼,注意不能丟擲檢查異常
        }
}
t.start; //啟動執行緒,注意 : 多次呼叫會出現IllegalThreadStateException

方法二:

 

Runnable r = new Runnable() {
    public void run(){
        // 要執行的程式碼
    }
};
Thread t = new Thread( r );
t.start();

注意 :
直接呼叫run和使用start間接呼叫run的區別:
        1) 直接呼叫run是在主執行緒中執行了run,沒有啟動新的執行緒
         2) 使用start是啟動新的執行緒,通過新的執行緒間接執行run

 

 

執行緒中常見方法

Thread.sleep(long n); // 讓當前執行緒休眠n毫秒
Thread.currentThread(); // 找到當前執行緒
join() : 等待該執行緒結束
join(long n ) : 等待該執行緒結束,但最多等待n毫秒
其它方法:
getName() : 得到執行緒的名稱
yield() : 謙讓
不推薦使用:
stop(),suspend,resume()

interrupt():
打斷正在等待的執行緒(例如sleep,join的等待)

 

Thread t = new Thread(()->{
    try {
        Thread.sleep(5000); // 被打斷執行緒會丟擲InterruptedException
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("結束 。。。");
});
t.start();
Thread.sleep(1000); //休眠一秒
System.out.println("打斷t執行緒。。。");
t.interrupt();

 

 

守護執行緒

setDaemon(true)
預設情況下,java程序需要等待所有執行緒都執行結束,才會結束
而守護執行緒,只要主執行緒執行結束,即使守護執行緒未執行完,也會跟著主執行緒一起結束

 

Thread t1= new Thread(()->{
    System.out.println("守護執行緒開始執行...");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("守護執行緒結束");
});
t1.setDaemon(true); // 設定該執行緒為守護執行緒
t1.start();
Thread.sleep(1000);

 

 

執行緒的併發(Concurrent)

synchronized(同步關鍵字)

語法

synchronized(物件) {
    要作為原子操作程式碼
}

作用 : 解決併發問題

static int i = 0;
static Object obj = new Object(); 
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { 
                i++;
            } 
        }
    });
    Thread t2 = new Thread(() -> { 
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { 
                i--;
            } 
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}//如果不處理併發,最終的值將不是0

 

每個物件有自己的monitor,當一個執行緒呼叫synchronized(物件),就相當於進入了這個物件的監視器。要檢查有沒有owner,如果沒有,此執行緒成為owner; 但如果已經有owner了,這個執行緒在entryset的區域等待owner的位置空出來。圖解如下:


注意 :

    1)synchronized必須是進入同一個物件的monitor 才有上述的效果
    2)兩個執行緒進入這個物件的監視器時,競爭是不分先後的,系統隨機

 

 

synchronized的另外兩種寫法

寫法一(只是常規寫法的物件固定為當前類物件):

public synchronized void test() {
}
等價於
public void test() {
    synchronized(this) {
    }
}

寫法二(常規寫法的物件固定為類.class):

```java
class Test{
	public synchronized static void test() {
	}
}
等價於
public static void test() {
	synchronized(Test.class) {
		
	}
}

 

 

volatile 易變的

用於修飾成員變數/靜態成員變數

優點 :
防止執行緒從自己的快取記憶體中查詢變數的值,必須到主存中獲取它的值
侷限性 :
只保證變數在多個執行緒間的可見性,不保證原子性

static boolean run = true;
public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while(run){
        }
    });
    t.start();
    Thread.sleep(1000);
    run = false;//對t執行緒不可見(導致執行緒無法停止),除非在定義run時新增volatile修飾符
}

相比之下,synchronized 語句塊既可以保證程式碼塊的原子性,也可以保證程式碼塊內變數的可見性。但缺點是synchronized是屬於重量級操作,效能會受到影響。

 

 

執行緒死鎖

一般來說,成為owner可以理解為獲得了物件的鎖,死鎖即是:
a執行緒獲得A物件的鎖,鎖裡還獲取B物件的鎖
b執行緒獲得B物件的鎖,鎖裡還獲得A物件的鎖

處理方法 : 儘可能避免

 

 

wait(),notify(),notifyAll()

前提 : 首先獲得物件鎖,否則無法使用wait,notify,notifyAll
特點 : 都屬於Object物件的方法
Object obj = new Object();
obj.wait(); 讓object監視器的執行緒等待
obj.notify(); 讓object上正在等待的執行緒中挑一個喚醒
obj.notifyAll(); 讓object上正在等待的執行緒全部喚醒

Object obj = new Object();
new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-0執行緒執行....");
        try {
            obj.wait(); // 讓執行緒在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-0其它程式碼....");
    }
}).start();
new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-1執行緒執行....");
        try {
            obj.wait(); // 讓執行緒在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-1其它程式碼....");
    }
}).start();
System.out.println("喚醒obj上其它執行緒");
synchronized (obj){
//            obj.notify();
    obj.notifyAll();
}

wait(long n) 有時限的等待, 到n毫秒後結束等待,或是被notify

 

sleep(long n) , wait(long n) 的區別

     1) sleep是Thread的方法,wait是Object的方法
     2) wait使用的前提是獲得物件鎖(使用了synchronized)
     3) sleep睡眠時不會釋放物件鎖,wait會釋放

 

 

執行緒的狀態

NEW (新建): 剛建立了執行緒,未start
RUANNABLE(可執行的): start之後
BLOCKED(阻塞) : 執行緒進入monitor監視區,然後在entrySet競爭owner位置
WAIT型別 :
1)WAITING(等待) : 呼叫了wait或者join方法進入WaitSet,處於等待狀態
2)TIMED_WAITING 當呼叫wait(long n) join(long n) 進入了WaitSet,處於有限時的等待狀態
TERMINATED (終止)當執行緒程式碼執行結束