Java Thread執行緒使用、執行緒安全(一)
一、參考
1、 java中的執行緒安全
2、 Java總結篇系列:Java多執行緒(一)
二、使用場景
1、耗時的操作使用執行緒(非同步操作),提高應用程式響應
2、並行操作時使用執行緒,如C/S架構的伺服器端併發執行緒響應使用者的請求(多執行緒)。
3 、多CPU系統中,使用執行緒提高CPU利用率
4、改善程式結構。一個既長又複雜的程序可以考慮分為多個執行緒,成為幾個獨立或半獨立的執行部分,這樣的程式會利於理解和修改。
三、如何使用
1、生命週期

thread_status.jpg
狀態 | 含義 | 內容 |
---|---|---|
New | 新建狀態 | 當執行緒物件對建立後,即進入了新建狀態,如:Thread t = new MyThread(); |
Runnable | 就緒狀態 | 當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了t.start()此執行緒立即就會執行; |
Running | 執行狀態 | 當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就 緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中; |
Blocked | 阻塞狀態 | 處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到執行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種: |
- | 等待阻塞 | 執行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態; |
- | 同步阻塞 | 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態; |
- | 其他阻塞 | 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。 |
Dead | 死亡狀態 | 執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。 |
2、建立啟動
已知Thread是實現Runable介面的,最終Runable中的run()方法才是最終需要執行的內容
2.1、新建Runable類例項,建立Thread時候入參Runable,這時候會執行Thread的init方法,會把此Runable變為Thread的一個Runable的類變數target
部分原始碼
/* What will be run. */ private Runnable target; public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } @Override public void run() { if (target != null) { target.run(); } }
啟動,結果是:runable running
/** * print: runable running * */ private Runnable myRun = new Runnable() { @Override public void run() { ComUtil.print("runable running"); } }; Thread a = new Thread(myRun); a.start();
2.2、新建自定義Thread類,然後複寫run方法,那麼如果此時建立的時候還入參一個runnable物件會怎麼樣呢?
啟動,結果:thread running , 雖然入參了變成Thread裡的target了,可是此thread複寫了Runable介面的run方法,所有不走父類的run了,直接走子類。如果還想走父類的run,那麼在run方法裡面寫:super.run();
/** * print: thread running * */ private Thread myThread = new Thread(myRun){ @Override public void run() { ComUtil.print("thread running"); } };
2.3、使用Callable和Future介面建立執行緒。。。
3、常用方法
方法 | 含義 |
---|---|
sleep() | 暫停阻塞等待一段時間,時間過了就繼續。 |
wait() | 也是阻塞和等待,但是需要notify來喚醒。 |
join() | 在一個執行緒中呼叫other.join(),將等待other執行完後才繼續本執行緒 |
notify()、notifyAll() | 喚醒執行緒 |
yield() | 當前執行緒可轉讓cpu控制權,讓別的就緒狀態執行緒執行(切換),也會等待阻塞一段時間,但是時間不是由客戶控制了 |
interrupte() | 打斷執行緒,代替過時方法stop() |
setPriority() | MIN_PRIORITY 最小優先順序=1 , NORM_PRIORITY 預設優先順序=5 ,MAX_PRIORITY 最大優先順序=10 |
public void testSleepAndWait(){ Thread t1 = new Thread(run1); t1.start(); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } Thread t2 = new Thread(run2); t2.start(); } private Runnable run1 = new Runnable() { @Override public void run() { synchronized (ThreadTest.class){ ComUtil.print("run_1 ready and waiting..."); try { //等待阻塞,等喚醒,如果不換型,就一直掛著 //然後釋放鎖,讓給其他執行緒使用 ThreadTest.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } ComUtil.print("run_1 running..."); ComUtil.print("run_1 dead"); } } }; private Runnable run2 = new Runnable() { @Override public void run() { synchronized (ThreadTest.class){ ComUtil.print("run_2 ready"); ComUtil.print("run_2 running and sleep..."); //喚醒阻塞的執行緒,重新進入監視鎖狀態,並準備搶鎖 ThreadTest.class.notify(); //不釋放鎖,僅僅睡一會,一會繼續 //直到完成,鎖才讓給其他等待執行的執行緒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } ComUtil.print("run_2 dead"); } } };
1、加入wait的喚醒,notify,執行結果如下
run_1 ready and waiting... run_2 ready run_2 running and sleep... run_2 dead run_1 running... run_1 dead
2、註釋掉notify
run_1 ready and waiting... run_2 ready run_2 running and sleep... run_2 dead
四、執行緒安全
1、一般說執行緒安全需要符合【原子性】和【可見性】和【順序的】,那麼使用多執行緒時候如果稍有不注意,很多操作就是非原子性的,會導致一些問題。
EG:最常見的一個案例:同一個時間,銀行三個櫃檯同時向一個賬戶存錢,因為執行緒間是不能互相傳遞資料的,這就導致操作同一個共享物件的記憶體在某個執行緒改變時候,在其他執行緒不會隨時更新,就會導致一個問題。
2、保證執行緒安全的常用方式
方式 | 含義 | 原則 |
---|---|---|
lock | 鎖 | 重量型(消耗記憶體較多),原子性,可見的,順序:公平鎖 、非順序:非公平鎖 |
synchronized | 同步的,互斥的 | 重量型(消耗記憶體較多),原子性,可見的 |
volitail | 易變的 | 輕量型(消耗記憶體較少),非原子性,可見的,主要針對共享變數,改變時候其他執行緒中也隨時會改變,但依舊是非原子性的,不是任何場景適合使用 |
共通部分程式碼
//不同地方,幾乎同一時間向同一個賬戶儲存10,20,70,總共100元 public void save(){ new Thread(){ @Override public void run() { saveMoney(1,10,this.getName()); } }.start(); new Thread(){ @Override public void run() { saveMoney(2,20,this.getName()); } }.start(); new Thread(){ @Override public void run() { saveMoney(3,70,this.getName()); } }.start(); try { Thread.sleep(1000); ComUtil.print("最後賬戶="+mAcount); } catch (InterruptedException e) { e.printStackTrace(); } }
1、Lock 鎖:那個執行緒先搶到鎖,其他執行緒呼叫到此方法時候休眠,直到這個執行緒釋放鎖,其他執行緒能繼續執行此方法
ReentrantLock 有機會再好好講一下
private Lock lock = new ReentrantLock();//注意這個地方 private void saveMoney(int time,int money,String tName){ lock.lock(); try { Thread.sleep(100); mAcount += money; ComUtil.print("執行緒:"+tName+",第【"+time+"】次存錢,賬戶="+mAcount); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } 結果:原子的,可見的,順序:公平鎖 、非順序:非公平鎖 執行緒:Thread-1,第【1】次存錢,賬戶=10 執行緒:Thread-2,第【2】次存錢,賬戶=30 執行緒:Thread-3,第【3】次存錢,賬戶=100 最後賬戶=100
2、synchronized 同步的,互斥的: 1、同步程式碼塊,非靜態同步,同步當前例項 2、同步程式碼塊,靜態同步,同步類.class 3、還可以修飾方法
private void saveMoney(int time,int money,String tName){ synchronized (this){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } mAcount += money; ComUtil.print("執行緒:"+tName+",第【"+time+"】次存錢,賬戶="+mAcount); } } private synchronized void saveMoney(int time,int money,String tName){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } mAcount += money; ComUtil.print("執行緒:"+tName+",第【"+time+"】次存錢,賬戶="+mAcount); } 結果:原子的,可見的,非順序的 執行緒:Thread-1,第【1】次存錢,賬戶=10 執行緒:Thread-3,第【3】次存錢,賬戶=80 執行緒:Thread-2,第【2】次存錢,賬戶=100 最後賬戶=100
3、volatile 易變的:他可以修飾共享變數,雖然符合【可見性】,但是依舊不是【原子性】,特別是執行緒中不能使用在對自身進行運算的操作上,但是他消耗記憶體較小,不會造成阻塞