線程基礎
線程的五種狀態
- 創建(New):使用new創建一個線程時的狀態,尚未啓動。
- 就緒(Runnable):調用start()啓動線程,加入就緒隊列,等待被調度進CPU運行
- 運行(Run):處於就緒狀態的線程獲取了cpu的執行權
- 阻塞(Block):由於某些原因該線程放棄了cpu的使用權。停止執行。除非線程進入可運行的狀態,纔會有機會獲取cpu的使用權。
- 等待阻塞:運行中的線程執行wait方法,這時候該線程會被放入等待隊列。
- 同步阻塞:運行中的線程獲取同步鎖,如果該同步鎖被別的線程佔用,這個線程會成被放入鎖池,等待其他線程釋放同步鎖。
- 其他阻塞:運行的線程執行sleep或者join方法這個線程會成爲阻塞狀態。當sleep超時,join等待線程終止,該線程會進入可運行狀態。
- 死亡(Dead):線程run mian 執行完畢後,或者因爲某些異常產生退出了 run 方法,該線程的生命週期結束。
線程狀態轉換圖
線程有關方法
- sleep(time):使線程進入阻塞狀態,但不釋放鎖,睡眠時間發到後自動進入就緒狀態。
作用:給其他線程執行機會。 - wait():使線程進入阻塞狀態,釋放鎖,等到被其他線程notify()時進入就緒狀態。
- interrupt():停止線程,調用時拋出中斷異常。
- setDaemon(true):後臺線程,當所有的前臺線程都結束了,後臺線程就結束。
注意:在啓動前調用。作用:爲前臺線程提供服務。 - setPriority(Thead.MAX_PRIORITY):設置線程的優先級(1-10),默認優先級爲5。
- join():當A線程執行到B線程的join()時,A就會等待,直到B結束A纔會執行。
作用:臨時加入線程,使該線程運行完,其他線程才繼續。 - yield():使當前線程放棄分得的CPU時間片,進入就緒狀態。
作用:使同優先級或更高優先級的線程有執行機會。
- sleep(time):使線程進入阻塞狀態,但不釋放鎖,睡眠時間發到後自動進入就緒狀態。
繼承Thread類
代碼演示
class Test extends Thread { Test(String name) { super(name); } //run()存儲線程要運行的代碼 public void run() { for(int x = 0; x < 60;x++) { System.out.println(Thread.currentThread().getName()+" run....."+x); } } } class ThreadDemo1 { public static void main(String[] args) { Test t1 = new Test("one"); Test t2 = new Test("two"); t1.start(); t2.start(); } }
存在的不足
- 子類無法多繼承。
- 不適合資源共享。
實現Runnable接口
代碼演示
class Ticket implements Runnable { private int tick = 100; public void run() { while(true) { if(tick>0) { System.out.println(Thread.currentThread().getName()+"sale:"+tick--); } } } } class ThreadDemo3 { public static void main(String[] args) { Ticket t = new Ticket(); //創建線程的同時明確線程要運行的代碼 Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); //啓動線程,使線程處於就緒狀態,等待被調度 t1.start(); t2.start(); t3.start(); t4.start(); } }
注意:實現Runnable接口的類不是線程類。創建線程對象的是Thread或Thread的子類。
多線程的安全問題
代碼演示安全問題
class Ticket implements Runnable { //線程的共享數據 private int tick = 100; public void run() { while(true) { if(tick>0) { //爲了演示可能出現的問題,讓當前線程釋放執行權 try{Thread.sleep(10);} catch(Exception e){} System.out.println(Thread.currentThread().getName()+"sale:"+tick--); } } } }
當多線程用到共享資源時,由於CPU的時間片特性,不管run()有沒有執行完畢,CPU時間片一旦執行完一個線程的時間片,會自動跳轉到另一個線程的run()中去執行其時間片,這樣就可能導致共享數據的不一致,也即出現了多線程安全問題。
解決方案
class Ticket implements Runnable { private int tick = 100; public void run() { while(true) { //在涉及到共享數據的代碼上加互斥鎖 synchronized(this) { if(tick>0) { try{Thread.sleep(10);} catch(Exception e){} System.out.println(Thread.currentThread().getName()+"sale:"+tick--); } } } } }
互斥鎖
互斥鎖的理解
互斥鎖相當於公共廁所的一把鎖,公共廁所是一個共享資源,爲了使當前正在使用資源的人不被其他人打擾,在門上加一把鎖,裏面的人不開門(釋放鎖),外面的人就進不去。
如何判斷哪些代碼需要加鎖
- 明確哪些代碼是多線程運行代碼
- 明確共享數據
- 明確多線程運行代碼中哪些語句是操作共享數據的。
加鎖後同步的前提
- 有兩個或兩個以上的線程
- 多個線程使用同一個鎖
加鎖的弊端
多個線程需要判斷鎖,較爲消耗資源。
鎖對象
- 同步代碼塊的鎖可以是任意對象。
- 同步函數的鎖是this。
- 靜態同步函數的鎖是Class對象。
多線程間的通信
- 問題描述
當多個線程在操作同一個資源,但是操作的動作不同(run()不同),就涉及到了線程間的通信問題。
解決方法:用標記+等待喚醒機制wait()和notify()實現同步
- wait():使正在執行的線程等待,進入阻塞狀態。
- notify():喚醒線程池中等待的線程,進入就緒狀態。
- notifyAll() :喚醒線程池中所有線程。
注意:這些方法都在互斥鎖中使用,因爲它們都被互斥鎖對象調用。 以下是代碼示例。
class Res { private String name; private String sex; private boolean flag = false; //是否釋放鎖的標記 //送入 public synchronized void set(String name,String sex) //加鎖 { if(flag) //有煤了,等,釋放鎖 try{this.wait();} catch(Exception e){} this.name = name; this.sex = sex; flag = true; //標記有資源了 this.notify(); //喚醒對方線程,叫對方送走資源 } //送走 public synchronized void out() { if(!flag) //沒有資源,等 try{this.wait();} catch(Exception e){} System.out.println(name+"........"+sex); flag = false; //標記沒有資源了 this.notify(); //喚醒對方線程,叫對方送資源 } } class Input implements Runnable { Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true) { if(x == 0) r.set("mike","male"); else r.set("lili","女女女女女女"); x = (x+1) % 2; } } } class Output implements Runnable { Res r; Output(Res r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ThreadDemo7 { public static void main(String[] args) { //要操作的共享資源 Res r = new Res(); //開啓兩個線程同時運行 new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
需要同步的幾種情況
- 兩個操作一樣的線程:直接加鎖(鎖對象是本身對象)
- 兩個操作不一樣的線程:加鎖+判斷標記+等待喚醒機制(notify())
- 兩個執行A操作的線程、兩個執行B操作的線程:加鎖+循環判斷標記+等待喚醒機制(notifyAll())
生產者消費者問題代碼示例
//共享資源 class Resource { private String name; //資源名稱 private int count = 1; //操作資源的次數 private boolean flag = false; //是否釋放鎖的標記 public synchronized void set(String name) //隱式的鎖機制 { while(flag) //有資源 try{this.wait();} catch(Exception e){} //等待的同時釋放鎖 this.name = name + "--" + count++; System.out.println(Thread.currentThread().getName()+"....生產者...."+this.name); flag = true; //有資源了 this.notifyAll(); //喚醒所有等待隊列中的線程 } public synchronized void out() { while(!flag) //沒有資源 try{this.wait();} catch(Exception e){} System.out.println(Thread.currentThread().getName()+"....消費者.........."+this.name); flag = false; //資源被取走了 this.notifyAll(); //喚醒所有等待隊列中的線程 } } //生產者 class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res = res; } public void run() { while(true) { res.set("+商品+"); } } } //消費者 class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while(true) { res.out(); } } } class ThreadDemo8 { public static void main(String[] args) { //共享資源對象 Resource r = new Resource(); //生產者、消費者對象操作同一個資源 Producer pro = new Producer(r); Consumer con = new Consumer(r); //兩個生產者線程、兩個消費者線程 Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }
JDK5.0升級後的同步解決方案
升級內容
- 將同步synchronized替換成Lock接口
- 由於wait()、notify()都要標識自己所屬的鎖,因此將Object中的wait()、notify()、 notifyAll()封裝成Condition監視器對象,該對象可以通過Lock鎖對象獲取,且一個鎖可以對應多個監視器對象。
升級後的好處
- 顯式的鎖機制,使加鎖和釋放鎖操作更透明。
- 顯式的等待喚醒機制,可以有多個Condition監視器對象,使不會喚醒本方。
- 由於一個鎖可以對應多個Condition監視器對象,因此不容易發生死鎖。
生產者消費者問題代碼示例
import java.util.concurrent.locks.*; //要操作的共享資源 class Resource { private String name; //資源名稱 private int count = 1; //操作資源的次數 private boolean flag = false; //是否釋放鎖的標記 private Lock lock = new ReentrantLock(); //接口引用指向子類對象 private Condition condition_pro = lock.newCondition(); //生產者監視器 private Condition condition_con = lock.newCondition(); //消費者監視器 //設置資源 public void set(String name) throws InterruptedException { lock.lock(); //加鎖 try { while(flag) condition_pro.await(); //生產者等待,沒有釋放鎖 this.name = name + "--" + count++; System.out.println(Thread.currentThread().getName()+"....生產者...."+this.name); flag = true; condition_con.signal(); //消費者醒來 } finally //在await()之前必須先釋放鎖 { lock.unlock(); //釋放鎖 } } //獲取資源 public void out() throws InterruptedException { lock.lock(); //加鎖 try { while(!flag) condition_con.await(); //消費者等待,沒有釋放鎖 System.out.println(Thread.currentThread().getName()+"....消費者.........."+this.name); flag = false; condition_pro.signal(); //生產者醒來 } finally //在await()之前必須先釋放鎖 { lock.unlock(); } } } //生產者 class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res = res; } public void run() { while(true) { try { res.set("+商品+"); } catch (InterruptedException e) { } } } } //消費者 class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while(true) { try { res.out(); } catch (InterruptedException e) { } } } } class ThreadDemo9 { public static void main(String[] args) { //創建共享資源對象 Resource r = new Resource(); //創建生產者消費者對象,操作同一個資源對象 Producer pro = new Producer(r); Consumer con = new Consumer(r); //兩個生產者線程,兩個消費者線程 Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); //開啓線程對象 t1.start(); t2.start(); t3.start(); t4.start(); } }