運(yùn)用示例詳細(xì)總結(jié)Java多線(xiàn)程
進(jìn)程與線(xiàn)程
進(jìn)程是程序的一次動(dòng)態(tài)執(zhí)行過(guò)程,它需要經(jīng)歷從代碼加載,代碼執(zhí)行到執(zhí)行完畢的一個(gè)完整的過(guò)程,這個(gè)過(guò)程也是進(jìn)程本身從產(chǎn)生,發(fā)展到最終消亡的過(guò)程。多進(jìn)程操作系統(tǒng)能同時(shí)達(dá)運(yùn)行多個(gè)進(jìn)程(程序),由于 CPU 具備分時(shí)機(jī)制,所以每個(gè)進(jìn)程都能循環(huán)獲得自己的CPU 時(shí)間片。由于 CPU 執(zhí)行速度非???,使得所有程序好像是在同時(shí)運(yùn)行一樣。
多線(xiàn)程是實(shí)現(xiàn)并發(fā)機(jī)制的一種有效手段。進(jìn)程和線(xiàn)程一樣,都是實(shí)現(xiàn)并發(fā)的一個(gè)基本單位。線(xiàn)程是比進(jìn)程更小的執(zhí)行單位,線(xiàn)程是進(jìn)程的基礎(chǔ)之上進(jìn)行進(jìn)一步的劃分。所謂多線(xiàn)程是指一個(gè)進(jìn)程在執(zhí)行過(guò)程中可以產(chǎn)生多個(gè)更小的程序單元,這些更小的單元稱(chēng)為線(xiàn)程,這些線(xiàn)程可以同時(shí)存在,同時(shí)運(yùn)行,一個(gè)進(jìn)程可能包含多個(gè)同時(shí)執(zhí)行的線(xiàn)程。進(jìn)程與線(xiàn)程的區(qū)別如圖所示:
Java中線(xiàn)程實(shí)現(xiàn)的方式
在 Java 中實(shí)現(xiàn)多線(xiàn)程有兩種手段,一種是繼承 Thread 類(lèi),另一種就是實(shí)現(xiàn) Runnable 接口。下面我們就分別來(lái)介紹這兩種方式的使用。
實(shí)現(xiàn) Runnable 接口
package ljz; class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口,作為線(xiàn)程的實(shí)現(xiàn)類(lèi) private String name ; // 表示線(xiàn)程的名稱(chēng) public MyThread(String name){ this.name = name ; // 通過(guò)構(gòu)造方法配置name屬性 } public void run(){ // 覆寫(xiě)run()方法,作為線(xiàn)程 的操作主體 for(int i=0;i<10;i++){ System.out.println(name + "運(yùn)行,i = " + i) ; } } }; public class RunnableDemo01{ public static void main(String args[]){ MyThread mt1 = new MyThread("線(xiàn)程A ") ; // 實(shí)例化對(duì)象 MyThread mt2 = new MyThread("線(xiàn)程B ") ; // 實(shí)例化對(duì)象 Thread t1 = new Thread(mt1) ; // 實(shí)例化Thread類(lèi)對(duì)象 Thread t2 = new Thread(mt2) ; // 實(shí)例化Thread類(lèi)對(duì)象 t1.start() ; // 啟動(dòng)多線(xiàn)程 t2.start() ; // 啟動(dòng)多線(xiàn)程 } };
程序運(yùn)行結(jié)果:
繼承 Thread 類(lèi)
class MyThread extends Thread{ // 繼承Thread類(lèi),作為線(xiàn)程的實(shí)現(xiàn)類(lèi) private String name ; // 表示線(xiàn)程的名稱(chēng) public MyThread(String name){ this.name = name ; // 通過(guò)構(gòu)造方法配置name屬性 } public void run(){ // 覆寫(xiě)run()方法,作為線(xiàn)程 的操作主體 for(int i=0;i<10;i++){ System.out.println(name + "運(yùn)行,i = " + i) ; } } }; public class ThreadDemo02{ public static void main(String args[]){ MyThread mt1 = new MyThread("線(xiàn)程A ") ; // 實(shí)例化對(duì)象 MyThread mt2 = new MyThread("線(xiàn)程B ") ; // 實(shí)例化對(duì)象 mt1.start() ; // 調(diào)用線(xiàn)程主體 mt2.start() ; // 調(diào)用線(xiàn)程主體 } };
程序運(yùn)行結(jié)果:
從程序可以看出,現(xiàn)在的兩個(gè)線(xiàn)程對(duì)象是交錯(cuò)運(yùn)行的,哪個(gè)線(xiàn)程對(duì)象搶到了 CPU 資源,哪個(gè)線(xiàn)程就可以運(yùn)行,所以程序每次的運(yùn)行結(jié)果肯定是不一樣的,在線(xiàn)程啟動(dòng)雖然調(diào)用的是 start() 方法,但實(shí)際上調(diào)用的卻是 run() 方法定義的主體。
Thread 類(lèi)和 Runnable 接口
通過(guò) Thread 類(lèi)和 Runable 接口都可以實(shí)現(xiàn)多線(xiàn)程,那么兩者有哪些聯(lián)系和區(qū)別呢?下面我們觀察 Thread 類(lèi)的定義。
public class Thread extends Object implements Runnable
從 Thread 類(lèi)的定義可以清楚的發(fā)現(xiàn),Thread 類(lèi)也是 Runnable 接口的子類(lèi),但在Thread類(lèi)中并沒(méi)有完全實(shí)現(xiàn) Runnable 接口中的 run() 方法,下面是 Thread 類(lèi)的部分定義。
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(); } }
從定義中可以發(fā)現(xiàn),在 Thread 類(lèi)中的 run() 方法調(diào)用的是 Runnable 接口中的 run() 方法,也就是說(shuō)此方法是由 Runnable 子類(lèi)完成的,所以如果要通過(guò)繼承 Thread 類(lèi)實(shí)現(xiàn)多線(xiàn)程,則必須覆寫(xiě) run()。
實(shí)際上 Thread 類(lèi)和 Runnable 接口之間在使用上也是有區(qū)別的,如果一個(gè)類(lèi)繼承 Thread類(lèi),則不適合于多個(gè)線(xiàn)程共享資源,而實(shí)現(xiàn)了 Runnable 接口,就可以方便的實(shí)現(xiàn)資源的共享。
線(xiàn)程的狀態(tài)變化
要想實(shí)現(xiàn)多線(xiàn)程,必須在主線(xiàn)程中創(chuàng)建新的線(xiàn)程對(duì)象。任何線(xiàn)程一般具有5種狀態(tài),即創(chuàng)建,就緒,運(yùn)行,阻塞,終止。下面分別介紹一下這幾種狀態(tài):
- 創(chuàng)建狀態(tài)
在程序中用構(gòu)造方法創(chuàng)建了一個(gè)線(xiàn)程對(duì)象后,新的線(xiàn)程對(duì)象便處于新建狀態(tài),此時(shí)它已經(jīng)有了相應(yīng)的內(nèi)存空間和其他資源,但還處于不可運(yùn)行狀態(tài)。新建一個(gè)線(xiàn)程對(duì)象可采用Thread 類(lèi)的構(gòu)造方法來(lái)實(shí)現(xiàn),例如 “Thread thread=new Thread()”。
- 就緒狀態(tài)
新建線(xiàn)程對(duì)象后,調(diào)用該線(xiàn)程的 start() 方法就可以啟動(dòng)線(xiàn)程。當(dāng)線(xiàn)程啟動(dòng)時(shí),線(xiàn)程進(jìn)入就緒狀態(tài)。此時(shí),線(xiàn)程將進(jìn)入線(xiàn)程隊(duì)列排隊(duì),等待 CPU 服務(wù),這表明它已經(jīng)具備了運(yùn)行條件。
- 運(yùn)行狀態(tài)
當(dāng)就緒狀態(tài)被調(diào)用并獲得處理器資源時(shí),線(xiàn)程就進(jìn)入了運(yùn)行狀態(tài)。此時(shí),自動(dòng)調(diào)用該線(xiàn)程對(duì)象的 run() 方法。run() 方法定義該線(xiàn)程的操作和功能。
- 阻塞狀態(tài)
一個(gè)正在執(zhí)行的線(xiàn)程在某些特殊情況下,如被人為掛起或需要執(zhí)行耗時(shí)的輸入/輸出操作,會(huì)讓 CPU 暫時(shí)中止自己的執(zhí)行,進(jìn)入阻塞狀態(tài)。在可執(zhí)行狀態(tài)下,如果調(diào)用sleep(),suspend(),wait() 等方法,線(xiàn)程都將進(jìn)入阻塞狀態(tài),發(fā)生阻塞時(shí)線(xiàn)程不能進(jìn)入排隊(duì)隊(duì)列,只有當(dāng)引起阻塞的原因被消除后,線(xiàn)程才可以轉(zhuǎn)入就緒狀態(tài)。
- 死亡狀態(tài)
線(xiàn)程調(diào)用 stop() 方法時(shí)或 run() 方法執(zhí)行結(jié)束后,即處于死亡狀態(tài)。處于死亡狀態(tài)的線(xiàn)程不具有繼續(xù)運(yùn)行的能力。
在此提出一個(gè)問(wèn)題,Java 程序每次運(yùn)行至少啟動(dòng)幾個(gè)線(xiàn)程?
回答:至少啟動(dòng)兩個(gè)線(xiàn)程,每當(dāng)使用 Java 命令執(zhí)行一個(gè)類(lèi)時(shí),實(shí)際上都會(huì)啟動(dòng)一個(gè) JVM,每一個(gè)JVM實(shí)際上就是在操作系統(tǒng)中啟動(dòng)一個(gè)線(xiàn)程,Java 本身具備了垃圾的收集機(jī)制。所以在 Java 運(yùn)行時(shí)至少會(huì)啟動(dòng)兩個(gè)線(xiàn)程,一個(gè)是 main 線(xiàn)程,另外一個(gè)是垃圾收集線(xiàn)程。
取得和設(shè)置線(xiàn)程的名稱(chēng)
class MyThread implements Runnable{ //實(shí)現(xiàn)Runnable接口 public void run(){ for(int i=0;i<3;i++){ System.Out.Println(Thread.currentThread().getName()+"運(yùn)行, i="+i); //取得當(dāng)前線(xiàn)程的名稱(chēng) } } }; public class ThreadDemo{ public static void main(String args[]){ MyThread my=new MyThread(); //定義Runnable子類(lèi)對(duì)象 new Thread(my).start; //系統(tǒng)自動(dòng)設(shè)置線(xiàn)程名稱(chēng) new Thread(my,"線(xiàn)程A").start(); //手工設(shè)置線(xiàn)程名稱(chēng) } };
程序運(yùn)行結(jié)果:
線(xiàn)程的操作方法
剛才在分析自定義模式工作原理的時(shí)候其實(shí)就已經(jīng)提到了,如果想要更改Glide的默認(rèn)配
線(xiàn)程的強(qiáng)制運(yùn)行
在線(xiàn)程操作中,可以使用 join() 方法讓一個(gè)線(xiàn)程強(qiáng)制運(yùn)行,線(xiàn)程強(qiáng)制運(yùn)行期間,其他線(xiàn)程無(wú)法運(yùn)行,必須等待此線(xiàn)程完成之后才可以繼續(xù)執(zhí)行。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口 public void run(){ // 覆寫(xiě)run()方法 for(int i=0;i<50;i++){ System.out.println(Thread.currentThread().getName() + "運(yùn)行,i = " + i) ; // 取得當(dāng)前線(xiàn)程的名字 } } }; public class ThreadJoinDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實(shí)例化Runnable子類(lèi)對(duì)象 Thread t = new Thread(mt,"線(xiàn)程"); // 實(shí)例化Thread對(duì)象 t.start() ; // 啟動(dòng)線(xiàn)程 for(int i=0;i<50;i++){ if(i>10){ try{ t.join() ; // 線(xiàn)程強(qiáng)制運(yùn)行 }catch(InterruptedException e){ } } System.out.println("Main線(xiàn)程運(yùn)行 --> " + i) ; } } };
程序運(yùn)行結(jié)果:
線(xiàn)程的休眠
在程序中允許一個(gè)線(xiàn)程進(jìn)行暫時(shí)的休眠,直接使用 Thread.sleep() 即可實(shí)現(xiàn)休眠。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口 public void run(){ // 覆寫(xiě)run()方法 for(int i=0;i<50;i++){ try{ Thread.sleep(500) ; // 線(xiàn)程休眠 }catch(InterruptedException e){ } System.out.println(Thread.currentThread().getName() + "運(yùn)行,i = " + i) ; // 取得當(dāng)前線(xiàn)程的名字 } } }; public class ThreadSleepDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實(shí)例化Runnable子類(lèi)對(duì)象 Thread t = new Thread(mt,"線(xiàn)程"); // 實(shí)例化Thread對(duì)象 t.start() ; // 啟動(dòng)線(xiàn)程 } };
程序執(zhí)行結(jié)果:
中斷線(xiàn)程
當(dāng)一個(gè)線(xiàn)程運(yùn)行時(shí),另外一個(gè)線(xiàn)程可以直接通過(guò)interrupt()方法中斷其運(yùn)行狀態(tài)。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口 public void run(){ // 覆寫(xiě)run()方法 System.out.println("1、進(jìn)入run()方法") ; try{ Thread.sleep(10000) ; // 線(xiàn)程休眠10秒 System.out.println("2、已經(jīng)完成了休眠") ; }catch(InterruptedException e){ System.out.println("3、休眠被終止") ; return ; // 返回調(diào)用處 } System.out.println("4、run()方法正常結(jié)束") ; } }; public class ThreadInterruptDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實(shí)例化Runnable子類(lèi)對(duì)象 Thread t = new Thread(mt,"線(xiàn)程"); // 實(shí)例化Thread對(duì)象 t.start() ; // 啟動(dòng)線(xiàn)程 try{ Thread.sleep(2000) ; // 線(xiàn)程休眠2秒 }catch(InterruptedException e){ System.out.println("3、休眠被終止") ; } t.interrupt() ; // 中斷線(xiàn)程執(zhí)行 } };
程序運(yùn)行結(jié)果是:
后臺(tái)線(xiàn)程
在 Java 程序中,只要前臺(tái)有一個(gè)線(xiàn)程在運(yùn)行,則整個(gè) Java 進(jìn)程都不會(huì)消失,所以此時(shí)可以設(shè)置一個(gè)后臺(tái)線(xiàn)程,這樣即使 Java 線(xiàn)程結(jié)束了,此后臺(tái)線(xiàn)程依然會(huì)繼續(xù)執(zhí)行,要想實(shí)現(xiàn)這樣的操作,直接使用 setDaemon() 方法即可。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口 public void run(){ // 覆寫(xiě)run()方法 while(true){ System.out.println(Thread.currentThread().getName() + "在運(yùn)行。") ; } } }; public class ThreadDaemonDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實(shí)例化Runnable子類(lèi)對(duì)象 Thread t = new Thread(mt,"線(xiàn)程"); // 實(shí)例化Thread對(duì)象 t.setDaemon(true) ; // 此線(xiàn)程在后臺(tái)運(yùn)行 t.start() ; // 啟動(dòng)線(xiàn)程 } };
在線(xiàn)程類(lèi) MyThread 中,盡管 run() 方法中是死循環(huán)的方式,但是程序依然可以執(zhí)行完,因?yàn)榉椒ㄖ兴姥h(huán)的線(xiàn)程操作已經(jīng)設(shè)置成后臺(tái)運(yùn)行。
線(xiàn)程的優(yōu)先級(jí)
在 Java 的線(xiàn)程操作中,所有的線(xiàn)程在運(yùn)行前都會(huì)保持在就緒狀態(tài),那么此時(shí),哪個(gè)線(xiàn)程的優(yōu)先級(jí)高,哪個(gè)線(xiàn)程就有可能會(huì)先被執(zhí)行。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口 public void run(){ // 覆寫(xiě)run()方法 for(int i=0;i<5;i++){ try{ Thread.sleep(500) ; // 線(xiàn)程休眠 }catch(InterruptedException e){ } System.out.println(Thread.currentThread().getName() + "運(yùn)行,i = " + i) ; // 取得當(dāng)前線(xiàn)程的名字 } } }; public class ThreadPriorityDemo{ public static void main(String args[]){ Thread t1 = new Thread(new MyThread(),"線(xiàn)程A") ; // 實(shí)例化線(xiàn)程對(duì)象 Thread t2 = new Thread(new MyThread(),"線(xiàn)程B") ; // 實(shí)例化線(xiàn)程對(duì)象 Thread t3 = new Thread(new MyThread(),"線(xiàn)程C") ; // 實(shí)例化線(xiàn)程對(duì)象 t1.setPriority(Thread.MIN_PRIORITY) ; // 優(yōu)先級(jí)最低 t2.setPriority(Thread.MAX_PRIORITY) ; // 優(yōu)先級(jí)最高 t3.setPriority(Thread.NORM_PRIORITY) ; // 優(yōu)先級(jí)最中等 t1.start() ; // 啟動(dòng)線(xiàn)程 t2.start() ; // 啟動(dòng)線(xiàn)程 t3.start() ; // 啟動(dòng)線(xiàn)程 } };
程序運(yùn)行結(jié)果:
從程序的運(yùn)行結(jié)果中可以觀察到,線(xiàn)程將根據(jù)其優(yōu)先級(jí)的大小來(lái)決定哪個(gè)線(xiàn)程會(huì)先運(yùn)行,但是需要注意并非優(yōu)先級(jí)越高就一定會(huì)先執(zhí)行,哪個(gè)線(xiàn)程先執(zhí)行將由 CPU 的調(diào)度決定。
線(xiàn)程的禮讓
在線(xiàn)程操作中,也可以使用 yield() 方法將一個(gè)線(xiàn)程的操作暫時(shí)讓給其他線(xiàn)程執(zhí)行
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口 public void run(){ // 覆寫(xiě)run()方法 for(int i=0;i<5;i++){ try{ Thread.sleep(500) ; }catch(Exception e){ } System.out.println(Thread.currentThread().getName() + "運(yùn)行,i = " + i) ; // 取得當(dāng)前線(xiàn)程的名字 if(i==2){ System.out.print("線(xiàn)程禮讓?zhuān)?) ; Thread.currentThread().yield() ; // 線(xiàn)程禮讓 } } } }; public class ThreadYieldDemo{ public static void main(String args[]){ MyThread my = new MyThread() ; // 實(shí)例化MyThread對(duì)象 Thread t1 = new Thread(my,"線(xiàn)程A") ; Thread t2 = new Thread(my,"線(xiàn)程B") ; t1.start() ; t2.start() ; } };
程序執(zhí)行結(jié)果:
同步以及死鎖
一個(gè)多線(xiàn)程的程序如果是通過(guò) Runnable 接口實(shí)現(xiàn)的,則意味著類(lèi)中的屬性被多個(gè)線(xiàn)程共享,那么這樣就會(huì)造成一種問(wèn)題,如果這多個(gè)線(xiàn)程要操作同一個(gè)資源時(shí)就有可能出現(xiàn)資源同步問(wèn)題。
解決方法:
同步代碼塊
synchronized(同步對(duì)象){
需要同步的代碼
}
class MyThread implements Runnable{ private int ticket = 5 ; // 假設(shè)一共有5張票 public void run(){ for(int i=0;i<100;i++){ synchronized(this){ // 要對(duì)當(dāng)前對(duì)象進(jìn)行同步 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣(mài)票:ticket = " + ticket-- ); } } } } }; public class SyncDemo02{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線(xiàn)程對(duì)象 Thread t1 = new Thread(mt) ; // 定義Thread對(duì)象 Thread t2 = new Thread(mt) ; // 定義Thread對(duì)象 Thread t3 = new Thread(mt) ; // 定義Thread對(duì)象 t1.start() ; t2.start() ; t3.start() ; } };
程序執(zhí)行結(jié)果:
同步方法
除了可以將需要的代碼設(shè)置成同步代碼塊外,也可以使用 synchronized 關(guān)鍵字將一個(gè)方法聲明為同步方法。
synchronized 方法返回值 方法名稱(chēng)(參數(shù)列表){
}
class MyThread implements Runnable{ private int ticket = 5 ; // 假設(shè)一共有5張票 public void run(){ for(int i=0;i<100;i++){ this.sale() ; // 調(diào)用同步方法 } } public synchronized void sale(){ // 聲明同步方法 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣(mài)票:ticket = " + ticket-- ); } } }; public class SyncDemo03{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線(xiàn)程對(duì)象 Thread t1 = new Thread(mt) ; // 定義Thread對(duì)象 Thread t2 = new Thread(mt) ; // 定義Thread對(duì)象 Thread t3 = new Thread(mt) ; // 定義Thread對(duì)象 t1.start() ; t2.start() ; t3.start() ; } };
程序執(zhí)行結(jié)果:
從程序運(yùn)行的結(jié)果可以發(fā)現(xiàn),此代碼完成了與之前同步代碼同樣的功能。
死鎖
同步可以保證資源共享操作的正確性,但是過(guò)多同步也會(huì)產(chǎn)生問(wèn)題。例如,現(xiàn)在張三想要李四的畫(huà),李四想要張三的書(shū),張三對(duì)李四說(shuō)“把你的畫(huà)給我,我就給你書(shū)”,李四也對(duì)張三說(shuō)“把你的書(shū)給我,我就給你畫(huà)”兩個(gè)人互相等對(duì)方先行動(dòng),就這么干等沒(méi)有結(jié)果,這實(shí)際上就是死鎖的概念。
所謂死鎖,就是兩個(gè)線(xiàn)程都在等待對(duì)方先完成,造成程序的停滯,一般程序的死鎖都是在程序運(yùn)行時(shí)出現(xiàn)的。
下面以一個(gè)簡(jiǎn)單范例說(shuō)明這個(gè)概念
class Zhangsan{ // 定義張三類(lèi) public void say(){ System.out.println("張三對(duì)李四說(shuō):“你給我畫(huà),我就把書(shū)給你?!?) ; } public void get(){ System.out.println("張三得到畫(huà)了。") ; } }; class Lisi{ // 定義李四類(lèi) public void say(){ System.out.println("李四對(duì)張三說(shuō):“你給我書(shū),我就把畫(huà)給你”") ; } public void get(){ System.out.println("李四得到書(shū)了。") ; } }; public class ThreadDeadLock implements Runnable{ private static Zhangsan zs = new Zhangsan() ; // 實(shí)例化static型對(duì)象 private static Lisi ls = new Lisi() ; // 實(shí)例化static型對(duì)象 private boolean flag = false ; // 聲明標(biāo)志位,判斷那個(gè)先說(shuō)話(huà) public void run(){ // 覆寫(xiě)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() ; } };
程序運(yùn)行結(jié)果:
以下代碼不再執(zhí)行,程序進(jìn)入死鎖狀態(tài)。
到此這篇關(guān)于Java多線(xiàn)程詳細(xì)總結(jié)的文章就介紹到這了,更多相關(guān)Java多線(xiàn)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
web.xml詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章給大家詳細(xì)介紹了web.xml的相關(guān)知識(shí),需要的朋友可以參考下2017-07-07Java并發(fā)應(yīng)用之任務(wù)執(zhí)行分析
這篇文章主要為大家詳細(xì)介紹了JavaJava并發(fā)應(yīng)用編程中任務(wù)執(zhí)行分析的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-07-07解決SpringBoot打成jar運(yùn)行后無(wú)法讀取resources里的文件問(wèn)題
這篇文章主要介紹了解決SpringBoot打成jar運(yùn)行后無(wú)法讀取resources里的文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08Java遞歸查找層級(jí)文件夾下特定內(nèi)容的文件的方法
這篇文章主要介紹了Java遞歸查找層級(jí)文件夾下特定內(nèi)容的文件,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06后端返回各種圖片形式在前端的轉(zhuǎn)換及展示方法對(duì)比
這篇文章主要給大家介紹了關(guān)于后端返回各種圖片形式在前端的轉(zhuǎn)換及展示方法對(duì)比的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-06-06mybatis-puls中的resultMap數(shù)據(jù)映射
這篇文章主要介紹了mybatis-puls中的resultMap數(shù)據(jù)映射,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Spring動(dòng)態(tài)代理實(shí)現(xiàn)日志功能詳解
這篇文章主要為大家詳細(xì)介紹了Spring動(dòng)態(tài)代理實(shí)現(xiàn)日志功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08