例項程式碼講解JAVA多執行緒
程序與執行緒
程序是程式的一次動態執行過程,它需要經歷從程式碼載入,程式碼執行到執行完畢的一個完整的過程,這個過程也是程序本身從產生,發展到最終消亡的過程。多程序作業系統能同時達執行多個程序(程式),由於 CPU 具備分時機制,所以每個程序都能迴圈獲得自己的CPU 時間片。由於 CPU 執行速度非常快,使得所有程式好像是在同時執行一樣。
多執行緒是實現併發機制的一種有效手段。程序和執行緒一樣,都是實現併發的一個基本單位。執行緒是比程序更小的執行單位,執行緒是程序的基礎之上進行進一步的劃分。所謂多執行緒是指一個程序在執行過程中可以產生多個更小的程式單元,這些更小的單元稱為執行緒,這些執行緒可以同時存在,同時執行,一個程序可能包含多個同時執行的執行緒。程序與執行緒的區別如圖所示:
Java中執行緒實現的方式
在 Java 中實現多執行緒有兩種手段,一種是繼承 Thread 類,另一種就是實現 Runnable 介面。下面我們就分別來介紹這兩種方式的使用。
實現 Runnable 介面
package ljz; class MyThread implements Runnable{ // 實現Runnable介面,作為執行緒的實現類 private String name ; // 表示執行緒的名稱 public MyThread(String name){ this.name = name ; // 通過構造方法配置name屬性 } public void run(){ // 覆寫run()方法,作為執行緒 的操作主體 for(int i=0;i<10;i++){ System.out.println(name + "執行,i = " + i) ; } } }; public class RunnableDemo01{ public static void main(String args[]){ MyThread mt1 = new MyThread("執行緒A ") ; // 例項化物件 MyThread mt2 = new MyThread("執行緒B ") ; // 例項化物件 Thread t1 = new Thread(mt1) ; // 例項化Thread類物件 Thread t2 = new Thread(mt2) ; // 例項化Thread類物件 t1.start() ; // 啟動多執行緒 t2.start() ; // 啟動多執行緒 } };
程式執行結果:
繼承 Thread 類
class MyThread extends Thread{ // 繼承Thread類,作為執行緒的實現類 private String name ; // 表示執行緒的名稱 public MyThread(String name){ this.name = name ; // 通過構造方法配置name屬性 } public void run(){ // 覆寫run()方法,作為執行緒 的操作主體 for(int i=0;i<10;i++){ System.out.println(name + "執行,i = " + i) ; } } }; public class ThreadDemo02{ public static void main(String args[]){ MyThread mt1 = new MyThread("執行緒A ") ; // 例項化物件 MyThread mt2 = new MyThread("執行緒B ") ; // 例項化物件 mt1.start() ; // 呼叫執行緒主體 mt2.start() ; // 呼叫執行緒主體 } };
程式執行結果:
從程式可以看出,現在的兩個執行緒物件是交錯執行的,哪個執行緒物件搶到了 CPU 資源,哪個執行緒就可以執行,所以程式每次的執行結果肯定是不一樣的,線上程啟動雖然呼叫的是 start() 方法,但實際上呼叫的卻是 run() 方法定義的主體。
Thread 類和 Runnable 介面
通過 Thread 類和 Runable 介面都可以實現多執行緒,那麼兩者有哪些聯絡和區別呢?下面我們觀察 Thread 類的定義。
public class Thread extends Object implements Runnable
從 Thread 類的定義可以清楚的發現,Thread 類也是 Runnable 介面的子類,但在Thread類中並沒有完全實現 Runnable 介面中的 run() 方法,下面是 Thread 類的部分定義。
Private Runnable target; public Thread(Runnable target,String name){ init(null,target,name,0); } private void init(ThreadGroup g,Runnable target,String name,long stackSize){ ... this.target=target; } public void run(){ if(target!=null){ target.run(); } }
從定義中可以發現,在 Thread 類中的 run() 方法呼叫的是 Runnable 介面中的 run() 方法,也就是說此方法是由 Runnable 子類完成的,所以如果要通過繼承 Thread 類實現多執行緒,則必須覆寫 run()。
實際上 Thread 類和 Runnable 介面之間在使用上也是有區別的,如果一個類繼承 Thread類,則不適合於多個執行緒共享資源,而實現了 Runnable 介面,就可以方便的實現資源的共享。
執行緒的狀態變化
要想實現多執行緒,必須在主執行緒中建立新的執行緒物件。任何執行緒一般具有5種狀態,即建立,就緒,執行,阻塞,終止。下面分別介紹一下這幾種狀態:
1 建立狀態
在程式中用構造方法建立了一個執行緒物件後,新的執行緒物件便處於新建狀態,此時它已經有了相應的記憶體空間和其他資源,但還處於不可執行狀態。新建一個執行緒物件可採用Thread 類的構造方法來實現,例如 “Thread thread=new Thread()”。
2 就緒狀態
新建執行緒物件後,呼叫該執行緒的 start() 方法就可以啟動執行緒。當執行緒啟動時,執行緒進入就緒狀態。此時,執行緒將進入執行緒佇列排隊,等待 CPU 服務,這表明它已經具備了執行條件。
3 執行狀態
當就緒狀態被呼叫並獲得處理器資源時,執行緒就進入了執行狀態。此時,自動呼叫該執行緒物件的 run() 方法。run() 方法定義該執行緒的操作和功能。
4 阻塞狀態
一個正在執行的執行緒在某些特殊情況下,如被人為掛起或需要執行耗時的輸入/輸出操作,會讓 CPU 暫時中止自己的執行,進入阻塞狀態。在可執行狀態下,如果呼叫sleep(),suspend(),wait() 等方法,執行緒都將進入阻塞狀態,發生阻塞時執行緒不能進入排隊佇列,只有當引起阻塞的原因被消除後,執行緒才可以轉入就緒狀態。
5 死亡狀態
執行緒呼叫 stop() 方法時或 run() 方法執行結束後,即處於死亡狀態。處於死亡狀態的執行緒不具有繼續執行的能力。
在此提出一個問題,Java 程式每次執行至少啟動幾個執行緒?
回答:至少啟動兩個執行緒,每當使用 Java 命令執行一個類時,實際上都會啟動一個 JVM,每一個JVM實際上就是在作業系統中啟動一個執行緒,Java 本身具備了垃圾的收集機制。所以在 Java 執行時至少會啟動兩個執行緒,一個是 main 執行緒,另外一個是垃圾收集執行緒。
取得和設定執行緒的名稱
class MyThread implements Runnable{ //實現Runnable介面 public void run(){ for(int i=0;i<3;i++){ System.Out.Println(Thread.currentThread().getName()+"執行,i="+i); //取得當前執行緒的名稱 } } }; public class ThreadDemo{ public static void main(String args[]){ MyThread my=new MyThread(); //定義Runnable子類物件 new Thread(my).start; //系統自動設定執行緒名稱 new Thread(my,"執行緒A").start(); //手工設定執行緒名稱 } };
程式執行結果:
執行緒的操作方法
剛才在分析自定義模式工作原理的時候其實就已經提到了,如果想要更改Glide的預設配
執行緒的強制執行
線上程操作中,可以使用 join() 方法讓一個執行緒強制執行,執行緒強制執行期間,其他執行緒無法執行,必須等待此執行緒完成之後才可以繼續執行。
class MyThread implements Runnable{ // 實現Runnable介面 public void run(){ // 覆寫run()方法 for(int i=0;i<50;i++){ System.out.println(Thread.currentThread().getName() + "執行,i = " + i) ; // 取得當前執行緒的名字 } } }; public class ThreadJoinDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 例項化Runnable子類物件 Thread t = new Thread(mt,"執行緒"); // 例項化Thread物件 t.start() ; // 啟動執行緒 for(int i=0;i<50;i++){ if(i>10){ try{ t.join() ; // 執行緒強制執行 }catch(InterruptedException e){ } } System.out.println("Main執行緒執行 --> " + i) ; } } };
程式執行結果:
執行緒的休眠
在程式中允許一個執行緒進行暫時的休眠,直接使用 Thread.sleep() 即可實現休眠。
class MyThread implements Runnable{ // 實現Runnable介面 public void run(){ // 覆寫run()方法 for(int i=0;i<50;i++){ try{ Thread.sleep(500) ; // 執行緒休眠 }catch(InterruptedException e){ } System.out.println(Thread.currentThread().getName() + "執行,i = " + i) ; // 取得當前執行緒的名字 } } }; public class ThreadSleepDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 例項化Runnable子類物件 Thread t = new Thread(mt,"執行緒"); // 例項化Thread物件 t.start() ; // 啟動執行緒 } };
程式執行結果:
中斷執行緒
當一個執行緒執行時,另外一個執行緒可以直接通過interrupt()方法中斷其執行狀態。
class MyThread implements Runnable{ // 實現Runnable介面 public void run(){ // 覆寫run()方法 System.out.println("1、進入run()方法") ; try{ Thread.sleep(10000) ; // 執行緒休眠10秒 System.out.println("2、已經完成了休眠") ; }catch(InterruptedException e){ System.out.println("3、休眠被終止") ; return ; // 返回呼叫處 } System.out.println("4、run()方法正常結束") ; } }; public class ThreadInterruptDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 例項化Runnable子類物件 Thread t = new Thread(mt,"執行緒"); // 例項化Thread物件 t.start() ; // 啟動執行緒 try{ Thread.sleep(2000) ; // 執行緒休眠2秒 }catch(InterruptedException e){ System.out.println("3、休眠被終止") ; } t.interrupt() ; // 中斷執行緒執行 } };
程式執行結果:
後臺執行緒
在 Java 程式中,只要前臺有一個執行緒在執行,則整個 Java 程序都不會消失,所以此時可以設定一個後臺執行緒,這樣即使 Java 執行緒結束了,此後臺執行緒依然會繼續執行,要想實現這樣的操作,直接使用 setDaemon() 方法即可。
class MyThread implements Runnable{ // 實現Runnable介面 public void run(){ // 覆寫run()方法 while(true){ System.out.println(Thread.currentThread().getName() + "在執行。") ; } } }; public class ThreadDaemonDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 例項化Runnable子類物件 Thread t = new Thread(mt,"執行緒"); // 例項化Thread物件 t.setDaemon(true) ; // 此執行緒在後臺執行 t.start() ; // 啟動執行緒 } };
線上程類 MyThread 中,儘管 run() 方法中是死迴圈的方式,但是程式依然可以執行完,因為方法中死迴圈的執行緒操作已經設定成後臺執行。
執行緒的優先順序
在 Java 的執行緒操作中,所有的執行緒在執行前都會保持在就緒狀態,那麼此時,哪個執行緒的優先順序高,哪個執行緒就有可能會先被執行。
class MyThread implements Runnable{ // 實現Runnable介面 public void run(){ // 覆寫run()方法 for(int i=0;i<5;i++){ try{ Thread.sleep(500) ; // 執行緒休眠 }catch(InterruptedException e){ } System.out.println(Thread.currentThread().getName() + "執行,i = " + i) ; // 取得當前執行緒的名字 } } }; public class ThreadPriorityDemo{ public static void main(String args[]){ Thread t1 = new Thread(new MyThread(),"執行緒A") ; // 例項化執行緒物件 Thread t2 = new Thread(new MyThread(),"執行緒B") ; // 例項化執行緒物件 Thread t3 = new Thread(new MyThread(),"執行緒C") ; // 例項化執行緒物件 t1.setPriority(Thread.MIN_PRIORITY) ; // 優先順序最低 t2.setPriority(Thread.MAX_PRIORITY) ; // 優先順序最高 t3.setPriority(Thread.NORM_PRIORITY) ; // 優先順序最中等 t1.start() ; // 啟動執行緒 t2.start() ; // 啟動執行緒 t3.start() ; // 啟動執行緒 } };
從程式的執行結果中可以觀察到,執行緒將根據其優先順序的大小來決定哪個執行緒會先執行,但是需要注意並非優先順序越高就一定會先執行,哪個執行緒先執行將由 CPU 的排程決定。
執行緒的禮讓
線上程操作中,也可以使用 yield() 方法將一個執行緒的操作暫時讓給其他執行緒執行
class MyThread implements Runnable{ // 實現Runnable介面 public void run(){ // 覆寫run()方法 for(int i=0;i<5;i++){ try{ Thread.sleep(500) ; }catch(Exception e){ } System.out.println(Thread.currentThread().getName() + "執行,i = " + i) ; // 取得當前執行緒的名字 if(i==2){ System.out.print("執行緒禮讓:") ; Thread.currentThread().yield() ; // 執行緒禮讓 } } } }; public class ThreadYieldDemo{ public static void main(String args[]){ MyThread my = new MyThread() ; // 例項化MyThread物件 Thread t1 = new Thread(my,"執行緒A") ; Thread t2 = new Thread(my,"執行緒B") ; t1.start() ; t2.start() ; } };
程式執行結果
同步以及死鎖
一個多執行緒的程式如果是通過 Runnable 介面實現的,則意味著類中的屬性被多個執行緒共享,那麼這樣就會造成一種問題,如果這多個執行緒要操作同一個資源時就有可能出現資源同步問題。
解決方法:
同步程式碼塊
synchronized(同步物件){ 需要同步的程式碼 }
class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ synchronized(this){ // 要對當前物件進行同步 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣票:ticket = " + ticket-- ); } } } } }; public class SyncDemo02{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義執行緒物件 Thread t1 = new Thread(mt) ; // 定義Thread物件 Thread t2 = new Thread(mt) ; // 定義Thread物件 Thread t3 = new Thread(mt) ; // 定義Thread物件 t1.start() ; t2.start() ; t3.start() ; } };
程式執行結果:
同步方法
除了可以將需要的程式碼設定成同步程式碼塊外,也可以使用 synchronized 關鍵字將一個方法宣告為同步方法。
synchronized 方法返回值 方法名稱(引數列表){ }
class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ this.sale() ; // 呼叫同步方法 } } public synchronized void sale(){ // 宣告同步方法 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣票:ticket = " + ticket-- ); } } }; public class SyncDemo03{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義執行緒物件 Thread t1 = new Thread(mt) ; // 定義Thread物件 Thread t2 = new Thread(mt) ; // 定義Thread物件 Thread t3 = new Thread(mt) ; // 定義Thread物件 t1.start() ; t2.start() ; t3.start() ; } };
程式執行結果
從程式執行的結果可以發現,此程式碼完成了與之前同步程式碼同樣的功能。
死鎖
同步可以保證資源共享操作的正確性,但是過多同步也會產生問題。例如,現在張三想要李四的畫,李四想要張三的書,張三對李四說“把你的畫給我,我就給你書”,李四也對張三說“把你的書給我,我就給你畫”兩個人互相等對方先行動,就這麼幹等沒有結果,這實際上就是死鎖的概念。
所謂死鎖,就是兩個執行緒都在等待對方先完成,造成程式的停滯,一般程式的死鎖都是在程式執行時出現的。
下面以一個簡單範例說明這個概念
class Zhangsan{ // 定義張三類 public void say(){ System.out.println("張三對李四說:“你給我畫,我就把書給你。”") ; } public void get(){ System.out.println("張三得到畫了。") ; } }; class Lisi{ // 定義李四類 public void say(){ System.out.println("李四對張三說:“你給我書,我就把畫給你”") ; } public void get(){ System.out.println("李四得到書了。") ; } }; public class ThreadDeadLock implements Runnable{ private static Zhangsan zs = new Zhangsan() ; // 例項化static型物件 private static Lisi ls = new Lisi() ; // 例項化static型物件 private boolean flag = false ; // 宣告標誌位,判斷那個先說話 public void run(){ // 覆寫run()方法 if(flag){ synchronized(zs){ // 同步張三 zs.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(ls){ zs.get() ; } } }else{ synchronized(ls){ ls.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(zs){ ls.get() ; } } } } public static void main(String args[]){ ThreadDeadLock t1 = new ThreadDeadLock() ; // 控制張三 ThreadDeadLock t2 = new ThreadDeadLock() ; // 控制李四 t1.flag = true ; t2.flag = false ; Thread thA = new Thread(t1) ; Thread thB = new Thread(t2) ; thA.start() ; thB.start() ; } };
程式執行結果
以下程式碼不再執行,程式進入死鎖狀態。
總結
至此關於多執行緒一些基本操作就介紹完了,鑑於筆者經驗有限,如果有什麼不足和缺漏的地方,歡迎相互交流學習,感謝大家!
以上就是例項程式碼講解JAVA多執行緒的詳細內容,更多關於JAVA多執行緒的資料請關注我們其它相關文章!