Thread 隨堂筆記
線程:一個程式裡的不同執行路徑。
線程的啟動:
方法一:實現Runnable介面(該介面只有一個方法 run)
方法二:從Thread繼承
Thread中的sleep方法:在哪個線程中調用Thread.sleep();哪個線程休息
Thread中的Join方法:在哪個線程中調用join();就把調用該方法的對象加入到該線程。
Thread中的yield方法:高風亮節把自己的線程先休息一會,讓其它線程執行,Thread.yield
Thread中的priority:setPriority, getPriority
線程同步:
關鍵字:synchronized 鎖定當前對象(在什麼地方加鎖呢,線上程共用的資源上加鎖。)
DeadLock現象 :有個兩個對象o1,o2 ;兩個線程t1,t2。線程t1鎖住了o1,線程t1鎖住 了o2,當線程t1再鎖住o2的時候它就能繼續執行,當線程t2在鎖住o1的時候它就 能繼續執行。但是o2被t1鎖住了,o1被t2鎖住了。誰也得不到能讓自己繼續執行 的對象,所以就產生了死結的現象。
wait與sleep的區別:
線程wait時,線程進入等待狀態,同時釋放鎖,如果沒有人叫醒它,它就會一直在 wait。(注意,線程並沒有死掉,而是wait)
線程sleep時,線程進入睡眠狀態,但是沒有釋放鎖,這時顯示不需要有人叫它就 會自己醒。
生產者消費者問題:介紹了wait()方法,notity()/notityAll()方法,的作用。
package com.liang.thread;public class ProduerConsumer {public static void main(String[] args) {SyncStack ss = new SyncStack();Produer p = new Produer(ss);Consumer c = new Consumer(ss);Thread tp = new Thread(p);Thread tp2 = new Thread(p);Thread tc = new Thread(c);tp.start();tp2.start();tc.start();System.out.println(Thread.activeCount()); //返回當前線程的線程組中活動線程的數目}}class ManTou {private int id;public ManTou(int id) {this.id = id;}public int getId() {return id;}public void setId(int id) {this.id = id;}}class SyncStack {ManTou[] mts = new ManTou[6];int index = 0;public synchronized void push(ManTou mt) { //把ManTou放進框子裡while(index == mts.length) {try {this.wait(); //讓當前線程進入等待狀態} catch (InterruptedException e) {e.printStackTrace();}}this.notify(); //叫醒在當前對象上等待的線程(但是不能叫醒線程本身)mts[index++] = mt;}public synchronized ManTou pop() { //把ManTou從框子裡拿出來while(index == 0) {try {this.wait(); //讓當前線程進入等待狀態} catch (InterruptedException e) {e.printStackTrace();}}this.notify(); //叫醒在當前對象上等待的線程(但是不能叫醒線程本身)return mts[--index];}}class Produer implements Runnable{ //生產者private SyncStack ss;public Produer(SyncStack ss) {this.ss = ss;}@Overridepublic void run() {for(int i=0; i<20; i++) {ManTou mt = new ManTou(i);ss.push(mt);System.out.println("生產了 :" + mt.getId());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}class Consumer implements Runnable{ //消費者private SyncStack ss;public Consumer(SyncStack ss) {this.ss = ss;}@Overridepublic void run() {for(int i=0; i<20; i++) { System.out.println("消費了 :" + ss.pop().getId()); try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}
Wait 和notify機制:wait/notify 機制是為了避免輪詢帶來的效能損失。
為了說清道理,我們用“圖書館借書”這個經典例子來作解釋。 一本書同時只能借給一個人。現在有一本書, 圖書館已經把這本書借了張三。 在簡單的synchrnozed 同步機制下,李四如果想借,先要去圖書館查看書有沒有還回 來。李四是個心急的人,他每天都去圖書館查;而張三看書看得慢,過了半個月才把書還回來,結果李四在這半個 月裡全都白跑了,浪費了不少交通車費
而如果使用wait/notify機制,李四就不用白忙了。他第一次去圖書館時發現書已借走,就回家靜靜等待(wait);張 三把書還掉後,通知(notify)李四,李四去圖書館拿書即可。整個過程中,李四沒有白跑,沒浪費錢。
回到電腦世界:
書 -- 臨界資源,需互斥地訪問
張三,李四 -- 兩個競爭的線程
坐車去圖書館查書 -- 輪詢
車費 -- CPU空間
等待 -- wait
通知下一個借書者 -- notify
也就是說,若使用簡單的synchonized機制實現互斥,會導致線程主動發起輪詢,若N次輪詢沒有成功,就產生了N次的CPU空間浪費;如果加上了 wait/notify機制,就可以避免這些無謂的輪詢,節省CPU的消耗。
補充:
wait與notify是java同步機制中重要的組成部分。結合與synchronized關鍵字使用,可以建立很多優秀的同步模型。
synchronized(this){}等價與public synchronized void method(){.....}
同步分為類層級和對象層級,分別對應著類鎖和對象鎖。類鎖是每個類只有一個,如果static的方法被synchronized關鍵字修飾,則在這個方法被執行前必須獲得類鎖;對象鎖類同。(static synchronized是類層級的,非static的synchronized和synchronized塊都是對象層級的,即作用在同一new出來的對象上)
首先,調用一個Object的wait與notify/notifyAll的時候,必須保證調用代碼對該Object是同步的,也就是說必須在作用等同於synchronized(obj){......}的內部才能夠去調用obj的wait與notify/notifyAll三個方法,否則就會報錯:
java.lang.IllegalMonitorStateException: current thread not owner
在調用wait的時候,線程自動釋放其佔有的對象鎖,同時不會去申請對象鎖。當線程被喚醒的時候,它才再次獲得了去獲得對象鎖的權利。
所以,notify與notifyAll沒有太多的區別,只是notify僅喚醒一個線程並允許它去獲得鎖,notifyAll是喚醒所有等待這個對象的線程並允許它們去獲得對象鎖,只要是在synchronied塊中的代碼,沒有對象鎖是寸步難行的。其實喚醒一個線程就是重新允許這個線程去獲得對象鎖並向下運行。 順便說一下notifyall,雖然是對每個wait的對象都調用一次notify,但是這個還是有順序的,每個對象都儲存這一個等待對象鏈,調用的順序就是這個鏈的順序。其實啟動等待對象鏈中各個線程的也是一個線程,在具體應用的時候,需要注意一下。