多執行緒基本知識
一、基本概念
一個應用程式就是一個程序,作業系統上可以有多個程序,一個程序中可以有多個執行緒。
一個CPU一個時刻只能執行一個執行緒,當有多個CPU時可以同時並行執行多個執行緒,但是執行緒數大於CPU數時,會有執行緒處於阻塞狀態。
二、執行緒的生命週期
狀態:新建、就緒、執行、阻塞、死亡。
阻塞:執行緒沒有獲取CPU資源,不能執行。
三、控制執行緒狀態
join()方法:在thread1中執行thread2_instance.join()方法時,thread1執行緒阻塞,thread2執行緒進入執行狀態;只有當thread2結束後thread1才會執行。
sleep()方法:thread1中呼叫Thread.sleep()方法時,thread1執行緒進入阻塞狀態,不釋放鎖
yield()方法:thread1中呼叫Thread.yield()方法時,thread1進入就緒狀態。
wait()、noitfy()、notifyAll()方法
四、執行緒同步
同步問題:當多個執行緒併發修改某個物件時,就會出現同步問題。
控制同步:
執行緒的目的是操作物件,那麼線上程類中把“目標物件作為成員變數”,然後“使用程式碼來操作、修改物件”。
使用鎖來控制同步,當某執行緒獲取鎖之後,只有執行完同步程式碼(中間沒呼叫wait()方法或沒有被異常中斷)才釋放鎖,其它執行緒才會獲取鎖來操作鎖物件。
1、同步程式碼塊
把操作修改物件的程式碼塊變成同步程式碼塊,目標物件作為鎖物件。
2、同步方法
把操作修改物件的程式碼塊變到物件類的例項方法中,把例項方法變成同步方法。通過物件呼叫此方法修改自己時,自己就是鎖物件。
3、使用lock物件
把可能出現同步問題的程式碼塊上鎖,執行完後解鎖。能保證
package grammar; import java.util.concurrent.locks.ReentrantLock; //被共同訪問、修改的物件 class Account { public int num; //鎖物件 private final ReentrantLock lock = new ReentrantLock(); public Account(int num) { this.num = num; } //呼叫此方法的物件就是“上鎖物件”,執行緒獲得該物件才能呼叫此方法 // public synchronized void draw(int drawAmout){ public void draw(int drawAmout){ //加鎖,使用Lock物件作為鎖物件 lock.lock(); if (num >= drawAmout) { System.out.println("開始取錢"); try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } num = num - drawAmout; System.out.println(num); System.out.println("結束取錢"); //釋放鎖 lock.unlock(); } } } // 修改物件的執行緒 class DrawThread extends Thread { // 被共訪修改的物件 private Account account; private int drawAmout; public DrawThread(Account account, int drawAmount) { this.account = account; this.drawAmout = drawAmount; } // 修改方法 public void run() { //account呼叫同步方法,則account是上鎖物件 account.draw(100); /**同步程式碼塊 //把共訪修改物件作為上鎖物件,只有執行緒獲得該物件才執行“同步程式碼塊” synchronized (account) { if (account.num >= drawAmout) { System.out.println("開始取錢"); try { // 強制讓執行緒阻塞 Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } account.num = account.num - drawAmout; System.out.println(account.num); System.out.println("結束取錢"); } } */ } } public class MyThread { public static void main(String[] args) { Account account = new Account(100); DrawThread thread1 = new DrawThread(account, 100); DrawThread thread2 = new DrawThread(account, 100); thread1.start(); thread2.start(); try { // main執行緒阻塞,先不列印 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主執行緒"); System.out.println(account.num); System.out.println("主執行緒"); } }
五、執行緒間通訊
藉助Object的三個方法:wait()、notify()、notifyAll(),只能在同步方法(直接呼叫)和同步程式碼塊(鎖物件呼叫)中呼叫。
wait()方法:在thread1中使用鎖物件.wait()方法,thread1進入阻塞狀態,釋放鎖。被notify()喚醒or超時後依然處於阻塞態,只有再次獲取鎖後才處於就緒態。而sleep()的執行緒醒後就是就緒態。
notify()方法:在thread1中使用鎖物件.notify()方法通知被wait()的執行緒後,thread1先執行完,再釋放鎖,其它需要獲得此鎖的執行緒才可能執行。若不使用notify()方法通知被wait()的執行緒,即使thread1執行完了釋放鎖,被wait()了的執行緒(超時後醒來可去競爭鎖物件)任然不能和其它執行緒進行競爭,從而獲得執行機會。