Java基礎(chǔ)之多線程方法狀態(tài)和創(chuàng)建方法
Java之線程的五大狀態(tài)及其常用方法(六個狀態(tài)還有timed_wating超時等待)
1.線程的五大狀態(tài)及其轉(zhuǎn)換
線程的五大狀態(tài)分別為:創(chuàng)建狀態(tài)(New)、就緒狀態(tài)(Runnable)、運行狀態(tài)(Running)、阻塞狀態(tài)(Blocked)、死亡狀態(tài)(Dead)。
下面畫出線程五大狀態(tài)之間的關(guān)系圖:
(1)新建狀態(tài):即單純地創(chuàng)建一個線程,創(chuàng)建線程有三種方式,在我的博客:線程的創(chuàng)建,可以自行查看!
(2)就緒狀態(tài):在創(chuàng)建了線程之后,調(diào)用Thread類的start()方法來啟動一個線程,即表示線程進入就緒狀態(tài)!
(3)運行狀態(tài):當(dāng)線程獲得CPU時間,線程才從就緒狀態(tài)進入到運行狀態(tài)!
(4)阻塞狀態(tài):線程進入運行狀態(tài)后,可能由于多種原因讓線程進入阻塞狀態(tài),如:調(diào)用sleep()方法讓線程睡眠,調(diào)用wait()方法讓線程等待,調(diào)用join()方法、suspend()方法(它現(xiàn)已被棄用!)以及阻塞式IO方法。
(5)死亡狀態(tài):run()方法的正常退出就讓線程進入到死亡狀態(tài),還有當(dāng)一個異常未被捕獲而終止了run()方法的執(zhí)行也將進入到死亡狀態(tài)!
2.設(shè)置或獲取多線程的線程名稱的方法
由于在一個進程中可能有多個線程,而多線程的運行狀態(tài)又是不確定的,即不知道在多線程中當(dāng)前執(zhí)行的線程是哪個線程,所以在多線程操作中需要有一個明確的標(biāo)識符標(biāo)識出當(dāng)前線程對象的信息,這個信息往往通過線程的名稱來描述。在Thread類中提供了一些設(shè)置或獲取線程名稱的方法:
(1)創(chuàng)建線程時設(shè)置線程名稱:
public Thread(Runnable target,String name)
(2)設(shè)置線程名稱的普通方法:
public final synchronized void setName(String name)
(3)取得線程名稱的普通方法:
public final String getName()
演示:
class MyThread implements Runnable{ @Override public void run() { for(int i=0;i<5;i++) { //currentThread()方法用于取得當(dāng)前正在JVM中運行的線程 //使用getName()方法,用于獲取線程的名稱 System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"-----i="+i); } } } public class Test1 { public static void main(String[] args){ //創(chuàng)建線程對象thread1且沒有設(shè)置線程名稱 MyThread myThread1=new MyThread(); Thread thread1=new Thread(myThread1); thread1.start(); //創(chuàng)建線程對象thread2且使用setName設(shè)置線程名稱 MyThread myThread2=new MyThread(); Thread thread2=new Thread(myThread2); thread2.setName("線程2"); thread2.start(); //創(chuàng)建線程對象thread3并在創(chuàng)建線程時設(shè)置線程名稱 MyThread myThread3=new MyThread(); Thread thread3=new Thread(myThread3,"線程3"); thread3.start(); } }
輸出:
當(dāng)前線程:Thread-0-----i=0
當(dāng)前線程:Thread-0-----i=1
當(dāng)前線程:Thread-0-----i=2
當(dāng)前線程:線程3-----i=0
當(dāng)前線程:線程2-----i=0
當(dāng)前線程:線程2-----i=1
當(dāng)前線程:線程2-----i=2
當(dāng)前線程:線程2-----i=3
當(dāng)前線程:線程3-----i=1
當(dāng)前線程:Thread-0-----i=3
當(dāng)前線程:Thread-0-----i=4
當(dāng)前線程:線程3-----i=2
當(dāng)前線程:線程3-----i=3
當(dāng)前線程:線程3-----i=4
當(dāng)前線程:線程2-----i=4
通過上述代碼及其運行結(jié)果可知:
(1)若沒有手動設(shè)置線程名稱時,會自動分配一個線程的名稱,如線程對象thread1自動分配線程名稱為Thread-0。
(2)多線程的運行狀態(tài)是不確定的,不知道下一個要執(zhí)行的是哪個線程,這是因為CPU以不確定方式或以隨機的時間調(diào)用線程中的run()方法。
(3)需要注意的是,由于設(shè)置線程名稱是為了區(qū)分當(dāng)前正在執(zhí)行的線程是哪一個線程,所以在設(shè)置線程名稱時應(yīng)避免重復(fù)!
3.線程休眠------sleep()方法
線程休眠:指的是讓線程暫緩執(zhí)行,等到預(yù)計時間之后再恢復(fù)執(zhí)行。
(1)線程休眠會交出CPU,讓CPU去執(zhí)行其他的任務(wù)。
(2)調(diào)用sleep()方法讓線程進入休眠狀態(tài)后,sleep()方法并不會釋放鎖,即當(dāng)前線程持有某個對象鎖時,即使調(diào)用sleep()方法其他線程也無法訪問這個對象。
(3)調(diào)用sleep()方法讓線程從運行狀態(tài)轉(zhuǎn)換為阻塞狀態(tài);sleep()方法調(diào)用結(jié)束后,線程從阻塞狀態(tài)轉(zhuǎn)換為可執(zhí)行狀態(tài)。
sleep()方法:
public static native void sleep(long millis) throws InterruptedException;
從上面方法參數(shù)中可以看出sleep()方法的休眠時間是以毫秒作為單位。
關(guān)于sleep()方法的操作如下:
class MyThread implements Runnable{ @Override public void run() { for(int i=0;i<5;i++) { //使用Thread類的sleep()方法,讓線程處于休眠狀態(tài) try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"-----i="+i); } } } public class Test1 { public static void main(String[] args){ MyThread myThread=new MyThread(); //利用myThread對象分別創(chuàng)建三個線程 Thread thread1=new Thread(myThread); thread1.start(); Thread thread2=new Thread(myThread); thread2.start(); Thread thread3=new Thread(myThread); thread3.start(); } }
某次運行結(jié)果如下所示:
當(dāng)前線程:Thread-2-----i=0
當(dāng)前線程:Thread-1-----i=0
當(dāng)前線程:Thread-0-----i=0
當(dāng)前線程:Thread-2-----i=1
當(dāng)前線程:Thread-0-----i=1
當(dāng)前線程:Thread-1-----i=1
當(dāng)前線程:Thread-2-----i=2
當(dāng)前線程:Thread-1-----i=2
當(dāng)前線程:Thread-0-----i=2
當(dāng)前線程:Thread-2-----i=3
當(dāng)前線程:Thread-1-----i=3
當(dāng)前線程:Thread-0-----i=3
當(dāng)前線程:Thread-2-----i=4
當(dāng)前線程:Thread-0-----i=4
當(dāng)前線程:Thread-1-----i=4
注:
(1)通過運行代碼進行觀察,發(fā)現(xiàn)運行結(jié)果會等待一段時間,這就是sleep()方法讓原本處于運行狀態(tài)的線程進入了休眠,從而進程的狀態(tài)從運行狀態(tài)轉(zhuǎn)換為阻塞狀態(tài)。
(2)以上代碼創(chuàng)建的三個線程肉眼觀察,發(fā)現(xiàn)它們好像是同時進入休眠狀態(tài),但其實并不是同時休眠的。
4.線程讓步------yield()方法
線程讓步:暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程。
(1)調(diào)用yield()方法讓當(dāng)前線程交出CPU權(quán)限,讓CPU去執(zhí)行其他線程。
(2)yield()方法和sleep()方法類似,不會釋放鎖,但yield()方法不能控制具體交出CPU的時間。
(3)yield()方法只能讓擁有相同優(yōu)先級的線程獲取CPU執(zhí)行的機會。
(4)使用yield()方法不會讓線程進入阻塞狀態(tài),而是讓線程從運行狀態(tài)轉(zhuǎn)換為就緒狀態(tài),只需要等待重新獲取CPU執(zhí)行的機會。
yield()方法:
public static native void yield();
關(guān)于yield()方法的操作如下:
class MyThread implements Runnable{ @Override public void run() { for(int i=0;i<5;i++) { //使用Thread類的yield()方法 Thread.yield(); System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"-----i="+i); } } } public class Test1 { public static void main(String[] args){ MyThread myThread=new MyThread(); //利用myThread對象分別創(chuàng)建三個線程 Thread thread1=new Thread(myThread); thread1.start(); Thread thread2=new Thread(myThread); thread2.start(); Thread thread3=new Thread(myThread); thread3.start(); } }
某次運行結(jié)果如下所示:
當(dāng)前線程:Thread-0-----i=0
當(dāng)前線程:Thread-0-----i=1
當(dāng)前線程:Thread-0-----i=2
當(dāng)前線程:Thread-0-----i=3
當(dāng)前線程:Thread-0-----i=4
當(dāng)前線程:Thread-1-----i=0
當(dāng)前線程:Thread-2-----i=0
當(dāng)前線程:Thread-2-----i=1
當(dāng)前線程:Thread-2-----i=2
當(dāng)前線程:Thread-2-----i=3
當(dāng)前線程:Thread-2-----i=4
當(dāng)前線程:Thread-1-----i=1
當(dāng)前線程:Thread-1-----i=2
當(dāng)前線程:Thread-1-----i=3
當(dāng)前線程:Thread-1-----i=4
5. 等待線程終止------join()方法
等待線程終止:指的是如果在主線程中調(diào)用該方法時就會讓主線程休眠,讓調(diào)用join()方法的線程先執(zhí)行完畢后再開始執(zhí)行主線程。
join()方法:
public final void join() throws InterruptedException { join(0); }
注:上面的join()方法是不帶參數(shù)的,但join()方法還可以帶參數(shù),下去自行了解!
關(guān)于join()方法的操作如下:
class MyThread implements Runnable{ @Override public void run() { for(int i=0;i<2;i++) { //使用Thread類的sleep()方法 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("當(dāng)前線程:"+Thread.currentThread().getName()+"-----i="+i); } } } public class Test1 { public static void main(String[] args) throws InterruptedException{ MyThread myThread=new MyThread(); Thread thread1=new Thread(myThread,"自己創(chuàng)建的線程"); thread1.start(); System.out.println("主線程:"+Thread.currentThread().getName()); //線程對象thread1調(diào)用join()方法 thread1.join(); System.out.println("代碼結(jié)束"); } }
運行結(jié)果如下所示:
主線程:main
當(dāng)前線程:自己創(chuàng)建的線程-----i=0
當(dāng)前線程:自己創(chuàng)建的線程-----i=1
代碼結(jié)束
若不調(diào)用join()方法的話,運行結(jié)果如下所示:
主線程:main
代碼結(jié)束
當(dāng)前線程:自己創(chuàng)建的線程-----i=0
當(dāng)前線程:自己創(chuàng)建的線程-----i=1
故通過兩個運行結(jié)果可以更加深刻地感受到調(diào)用join()方法后的作用!調(diào)用join()方法和不調(diào)用join()方法的區(qū)別!
6. 線程停止
多線程中停止線程有三種方式:
(1)設(shè)置標(biāo)記位,讓線程正常停止。
class MyThread implements Runnable{ //設(shè)置標(biāo)記位 private boolean flag=true; public void setFlag(boolean flag) { this.flag = flag; } @Override public void run() { int i=0; while(flag) { System.out.println("第"+(i++)+"次執(zhí)行-----"+"線程名稱:"+Thread.currentThread().getName()); } } } public class Test1 { public static void main(String[] args) throws InterruptedException{ MyThread myThread=new MyThread(); Thread thread1=new Thread(myThread,"自己創(chuàng)建的線程"); thread1.start(); //讓主線程sleep一毫秒 Thread.sleep(1); //修改標(biāo)記位的值,讓自己創(chuàng)建的線程停止 myThread.setFlag(false); System.out.println("代碼結(jié)束"); } }
運行結(jié)果如下所示:
第0次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第1次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第2次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第3次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第4次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第5次執(zhí)行-----線程名稱:自己創(chuàng)..............
第19次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第20次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第21次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第22次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第23次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第24次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第25次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
第26次執(zhí)行-----線程名稱:自己創(chuàng)建的線程
代碼結(jié)束
(2)使用stop()方法強制使線程退出,但是使用該方法不安全,已經(jīng)被廢棄了!
class MyThread implements Runnable{ @Override public void run() { for(int i=0;i<100;i++) { System.out.println("線程名稱:"+Thread.currentThread().getName()+"------i="+i); } } } public class Test1 { public static void main(String[] args) throws InterruptedException{ MyThread myThread=new MyThread(); Thread thread1=new Thread(myThread,"自己創(chuàng)建的線程"); thread1.start(); //讓主線程sleep一毫秒 Thread.sleep(1); //調(diào)用已被棄用的stop()方法去強制讓線程退出 thread1.stop(); System.out.println("代碼結(jié)束"); } }
某次運行結(jié)果如下所示:
線程名稱:自己創(chuàng)建的線程------i=0
線程名稱:自己創(chuàng)建的線程------i=1
線程名稱:自己創(chuàng)建的線程------i=2
線程名稱:自己創(chuàng)建的線程------i=3
線程名稱:自己創(chuàng)建的線程------i=4
線程名稱:自己創(chuàng)建的線程------i=5
線程名稱:自己創(chuàng)建的線程------i=6
......
線程名稱:自己創(chuàng)建的線程------i=28
線程名稱:自己創(chuàng)建的線程------i=29
線程名稱:自己創(chuàng)建的線程------i=30
線程名稱:自己創(chuàng)建的線程------i=31
線程名稱:自己創(chuàng)建的線程------i=48
線程名稱:自己創(chuàng)建的線程------i=49
線程名稱:自己創(chuàng)建的線程------i=50
線程名稱:自己創(chuàng)建的線程------i=51線程名稱:自己創(chuàng)建的線程------i=51代碼結(jié)束
從上述代碼和運行結(jié)果可以看出,原本線程對象thread1的run()方法中應(yīng)該執(zhí)行100次語句“System.out.println(“線程名稱:”+Thread.currentThread().getName()+"------i="+i);”,但現(xiàn)在沒有執(zhí)行夠100次,所以說stop()方法起到了讓線程終止的作用。再從運行結(jié)果上可以看出,i=51被執(zhí)行了兩次且沒有換行,這就體現(xiàn)了調(diào)用stop()方法的不安全性!
下面正式地解釋stop()方法為什么不安全?
因為stop()方法會解除由線程獲得的所有鎖,當(dāng)在一個線程對象上調(diào)用stop()方法時,這個線程對象所運行的線程會立即停止,假如一個線程正在執(zhí)行同步方法:
public synchronized void fun(){ x=3; y=4; }
由于方法是同步的,多線程訪問時總能保證x,y被同時賦值,而如果一個線程正在執(zhí)行到x=3;時,被調(diào)用的stop()方法使得線程即使在同步方法中也要停止,這就造成了數(shù)據(jù)的不完整性。故,stop()方法不安全,已經(jīng)被廢棄,不建議使用!
(3)使用Thread類的interrupt()方法中斷線程。
class MyThread implements Runnable{ @Override public void run() { int i=0; while(true) { //使用sleep()方法,使得線程由運行狀態(tài)轉(zhuǎn)換為阻塞狀態(tài) try { Thread.sleep(1000); //調(diào)用isInterrupted()方法,用于判斷當(dāng)前線程是否被中斷 boolean bool=Thread.currentThread().isInterrupted(); if(bool) { System.out.println("非阻塞狀態(tài)下執(zhí)行該操作,當(dāng)前線程被中斷!"); break; } System.out.println("第"+(i++)+"次執(zhí)行"+" 線程名稱:"+Thread.currentThread().getName()); } catch (InterruptedException e) { System.out.println("退出了!"); //這里退出了阻塞狀態(tài),且中斷標(biāo)志bool被系統(tǒng)自動清除設(shè)置為false,所以此處的bool為false boolean bool=Thread.currentThread().isInterrupted(); System.out.println(bool); //退出run()方法,中斷進程 return; } } } } public class Test1 { public static void main(String[] args) throws InterruptedException{ MyThread myThread=new MyThread(); Thread thread1=new Thread(myThread,"自己創(chuàng)建的線程"); thread1.start(); //讓主線程sleep三秒 Thread.sleep(3000); //調(diào)用interrupt()方法 thread1.interrupt(); System.out.println("代碼結(jié)束"); } }
運行結(jié)果如下所示 :
第0次執(zhí)行 線程名稱:自己創(chuàng)建的線程
第1次執(zhí)行 線程名稱:自己創(chuàng)建的線程
第2次執(zhí)行 線程名稱:自己創(chuàng)建的線程
代碼結(jié)束
退出了!
false
(1)interrupt()方法只是改變中斷狀態(tài)而已,它不會中斷一個正在運行的線程。具體來說就是,調(diào)用interrupt()方法只會給線程設(shè)置一個為true的中斷標(biāo)志,而設(shè)置之后,則根據(jù)線程當(dāng)前狀態(tài)進行不同的后續(xù)操作。
(2)如果線程的當(dāng)前狀態(tài)出于非阻塞狀態(tài),那么僅僅將線程的中斷標(biāo)志設(shè)置為true而已;
(3)如果線程的當(dāng)前狀態(tài)出于阻塞狀態(tài),那么將在中斷標(biāo)志設(shè)置為true后,還會出現(xiàn)wait()、sleep()、join()方法之一引起的阻塞,那么會將線程的中斷標(biāo)志位重新設(shè)置為false,并拋出一個InterruptedException異常。
(4)如果在中斷時,線程正處于非阻塞狀態(tài),則將中斷標(biāo)志修改為true,而在此基礎(chǔ)上,一旦進入阻塞狀態(tài),則按照阻塞狀態(tài)的情況來進行處理。例如,一個線程在運行狀態(tài)時,其中斷標(biāo)志設(shè)置為true之后,一旦線程調(diào)用了wait()、sleep()、join()方法中的一種,立馬拋出一個InterruptedException異常,且中斷標(biāo)志被程序自動清除,重新設(shè)置為false。
總結(jié):調(diào)用Thread類的interrupted()方法,其本質(zhì)只是設(shè)置該線程的中斷標(biāo)志,將中斷標(biāo)志設(shè)置為true,并根據(jù)線程狀態(tài)決定是否拋出異常。因此,通過interrupted()方法真正實現(xiàn)線程的中斷原理是 :開發(fā)人員根據(jù)中斷標(biāo)志的具體值來決定如何退出線程。
7. 線程等待------wait()方法
首先,wait()方法是Object類的方法,下面是無參的wait()方法:
public final void wait() throws InterruptedException { wait(0); }
(1)wait()方法的作用是讓當(dāng)前正在執(zhí)行的線程進入線程阻塞狀態(tài)的等待狀態(tài),該方法時用來將當(dāng)前線程置入“預(yù)執(zhí)行隊列”中,并且調(diào)用wait()方法后,該線程在wait()方法所在的代碼處停止執(zhí)行,直到接到一些通知或被中斷為止。
(2)wait()方法只能在同步代碼塊或同步方法中調(diào)用,故如果調(diào)用wait()方法時沒有持有適當(dāng)?shù)逆i時,就會拋出異常。
(3)wait()方法執(zhí)行后,當(dāng)前線程釋放鎖并且與其他線程相互競爭重新獲得鎖。
下面調(diào)用wait()方法:
public class Test1 { public static void main(String[] args) { Object object=new Object(); synchronized (object) { System.out.println("調(diào)用wait()前"); //調(diào)用Object類的wait()方法 try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("調(diào)用wait()后"); } } }
運行結(jié)果如下所示:
調(diào)用wait()前
解析:此時,程序依然處于執(zhí)行狀態(tài)。原本應(yīng)該打印兩條語句:調(diào)用wait()前 調(diào)用wait()后,但是由于該程序還沒有運行完而且只打印了一條語句:調(diào)用wait()前。這是因為什么呢?因為調(diào)用了Object類的wait()方法,使得程序在執(zhí)行wait()方法之后一直等待下去,故只執(zhí)行了調(diào)用wait()方法前的語句。但程序不能這樣一直等待下去,這個時候就需要另一個方法去喚醒調(diào)用wait()方法的處于等待狀態(tài)的線程,讓等待線程繼續(xù)執(zhí)行下去,該方法為notify()方法。
8. 線程喚醒-------notify()方法
首先,notify()方法也是Object類的方法:
public final native void notify();
(1)notify()方法要在同步代碼塊或同步方法中調(diào)用。
(2)notify()方法是用來通知那些等待該對象的對象鎖的線程,對其調(diào)用wait()方法的對象發(fā)出通知讓這些線程不再等待,繼續(xù)執(zhí)行。
(3)如果有多個線程都在等待,則由線程規(guī)劃器隨機挑選出一個呈wait狀態(tài)的線程將其線程喚醒,繼續(xù)執(zhí)行該線程。
(4)調(diào)用notify()方法后,當(dāng)前線程并不會馬上釋放該對象鎖,要等到執(zhí)行notify()方法的線程執(zhí)行完才會釋放對象鎖。
下面調(diào)用notify()方法:
class MyThread implements Runnable{ private boolean flag; private Object object; //定義一個構(gòu)造方法 public MyThread(boolean flag,Object object) { this.flag=flag; this.object=object; } //定義一個普通方法,其中調(diào)用了wait()方法 public void waitThread() { synchronized (this.object) { try { System.out.println("調(diào)用wait()前------"+Thread.currentThread().getName()); //調(diào)用wait()方法 this.object.wait(); System.out.println("調(diào)用wait()后------"+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } //定義一個普通方法,其中調(diào)用了notify()方法 public void notifyThread() { synchronized (this.object) { try { System.out.println("調(diào)用notify前------"+Thread.currentThread().getName()); //調(diào)用notify()方法 this.object.notify(); System.out.println("調(diào)用notify()后------"+Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } } } @Override public void run() { if(this.flag) { this.waitThread(); }else { this.notifyThread(); } } } public class Test1 { public static void main(String[] args) { Object object=new Object(); //實例化調(diào)用wait()的線程 MyThread wait=new MyThread(true,object); Thread waitThread=new Thread(wait,"wait線程"); //實例化調(diào)用notify()的線程 MyThread notify=new MyThread(false,object); Thread notifyThread=new Thread(notify,"notify線程"); //啟動線程 waitThread.start(); //調(diào)用一下sleep()方法,使得查看效果更明顯 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } notifyThread.start(); } }
運行結(jié)果
調(diào)用wait()前------wait線程
調(diào)用notify前------notify線程
調(diào)用notify()后------notify線程
調(diào)用wait()后------wait線程
解析:根據(jù)run方法及實例化的線程對象,wait線程執(zhí)行了waitThread()方法,該方法中調(diào)用了wait()方法使得線程進入線程阻塞狀態(tài)的等待狀態(tài)并釋放鎖,如果沒有其他線程去喚醒該線程的話wait線程將一直等待下去。此時,notify線程執(zhí)行了notifyThread()方法,該方法中調(diào)用了notify()方法,使得notify線程去喚醒wait線程,讓wait線程不再等待下去,并且先將notify線程執(zhí)行完后釋放鎖再執(zhí)行wait線程的wait()方法之后的語句。所以打印如上所示。
但要注意的是,當(dāng)有多個線程處于等待時,調(diào)用notify()方法喚醒線程時,就會依然有線程處于等待狀態(tài),演示如下:
class MyThread implements Runnable{ private boolean flag; private Object object; //定義一個構(gòu)造方法 public MyThread(boolean flag,Object object) { this.flag=flag; this.object=object; } //定義一個普通方法,其中調(diào)用了wait()方法 public void waitThread() { synchronized (this.object) { try { System.out.println("調(diào)用wait()前------"+Thread.currentThread().getName()); //調(diào)用wait()方法 this.object.wait(); System.out.println("調(diào)用wait()后------"+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } //定義一個普通方法,其中調(diào)用了notify()方法 public void notifyThread() { synchronized (this.object) { try { System.out.println("調(diào)用notify前------"+Thread.currentThread().getName()); //調(diào)用notify()方法 this.object.notify(); System.out.println("調(diào)用notify()后------"+Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } } } @Override public void run() { if(this.flag) { this.waitThread(); }else { this.notifyThread(); } } } public class Test1 { public static void main(String[] args) { Object object=new Object(); //實例化調(diào)用wait()的線程 MyThread wait=new MyThread(true,object); Thread waitThread1=new Thread(wait,"wait線程1"); Thread waitThread2=new Thread(wait,"wait線程2"); Thread waitThread3=new Thread(wait,"wait線程3"); //實例化調(diào)用notify()的線程 MyThread notify=new MyThread(false,object); Thread notifyThread=new Thread(notify,"notify線程"); //啟動3個等待線程 waitThread1.start(); waitThread2.start(); waitThread3.start(); //調(diào)用一下sleep()方法,使得查看效果更明顯 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } notifyThread.start(); } }
運行結(jié)果如下所示:
調(diào)用wait()前------wait線程1
調(diào)用wait()前------wait線程3
調(diào)用wait()前------wait線程2
調(diào)用notify前------notify線程
調(diào)用notify()后------notify線程
調(diào)用wait()后------wait線程1
此時程序并沒有執(zhí)行完畢,因為依然有線程處于等待狀態(tài),所以notify()只是隨機將某一個等待線程喚醒,并沒有喚醒所有等待線程。那么,若有多個線程處于等待狀態(tài)時,如何將所有等待線程都喚醒呢?下面將介紹notifyAll()方法
9. notifyAll()方法
Object類的notifyAll()方法:
public final native void notifyAll();
notifyAll()方法將同一對象鎖的所有等待線程全部喚醒。代碼演示如下:
class MyThread implements Runnable{ private boolean flag; private Object object; //定義一個構(gòu)造方法 public MyThread(boolean flag,Object object) { this.flag=flag; this.object=object; } //定義一個普通方法,其中調(diào)用了wait()方法 public void waitThread() { synchronized (this.object) { try { System.out.println("調(diào)用wait()前------"+Thread.currentThread().getName()); //調(diào)用wait()方法 this.object.wait(); System.out.println("調(diào)用wait()后------"+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } //定義一個普通方法,其中調(diào)用了notifyAll()方法 public void notifyThread() { synchronized (this.object) { try { System.out.println("調(diào)用notify前------"+Thread.currentThread().getName()); //調(diào)用notifyAll()方法 this.object.notifyAll(); System.out.println("調(diào)用notify()后------"+Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } } } @Override public void run() { if(this.flag) { this.waitThread(); }else { this.notifyThread(); } } } public class Test1 { public static void main(String[] args) { Object object=new Object(); //實例化調(diào)用wait()的線程 MyThread wait=new MyThread(true,object); Thread waitThread1=new Thread(wait,"wait線程1"); Thread waitThread2=new Thread(wait,"wait線程2"); Thread waitThread3=new Thread(wait,"wait線程3"); //實例化調(diào)用notifyAll()的線程 MyThread notify=new MyThread(false,object); Thread notifyThread=new Thread(notify,"notify線程"); //啟動3個等待線程 waitThread1.start(); waitThread2.start(); waitThread3.start(); //調(diào)用一下sleep()方法,使得查看效果更明顯 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } notifyThread.start(); } }
運行結(jié)果如下所示:
調(diào)用wait()前------wait線程1
調(diào)用wait()前------wait線程2
調(diào)用wait()前------wait線程3
調(diào)用notify前------notify線程
調(diào)用notify()后------notify線程
調(diào)用wait()后------wait線程3
調(diào)用wait()后------wait線程2
調(diào)用wait()后------wait線程1
解析:此時,程序執(zhí)行完畢,所有等待線程都被調(diào)用notifyAll()方法的具有同一對象鎖的線程喚醒,故每一個等待線程都會在調(diào)用wait()后繼續(xù)執(zhí)行直到該線程結(jié)束。
JAVA多線程有哪幾種實現(xiàn)方式?
1. 繼承Thread類
1)定義Thread類的子類,并重寫Thread類的run()方法。
2)創(chuàng)建Thread子類的實例,及創(chuàng)建了線程對象。
3)調(diào)用線程對象的start()方法來啟動該線程。
class MyThread extends Thread{ public void run(){ System.out.println("線程運行"); } } public class Test{ public static void main(String[] args){ MyThread thread=new MyThread(); thread.start();//開啟線程 } }
Thread類常用方法
Thread.currentThread():是Thread類的靜態(tài)方法,該方法總是返回當(dāng)前正在執(zhí)行的線程對象。
String getName():該方法是Thread類的實例方法,是返回調(diào)用該方法的線程名字。
2. 實現(xiàn)Runnable接口
1)定義Runnable接口的實現(xiàn)類,并重寫該接口的run()方法,該run()方法同樣是線程執(zhí)行體。
2)創(chuàng)建Runnable實現(xiàn)類的實例,并以此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。
3)調(diào)用線程對象的start()方法來啟動該線程。
class Runna implements Runnable { public void run(){ System.out.println("線程運行"); } } public class Test{ public static void main(String[] args){ Runna runna=new Runna(); Thread t=new Thread(runna); t.start();//開啟線程 } }
推薦使用那種:很明顯,我們在使用繼承Thread方式時,有一個很大的缺點,就是我們繼承了Thread后就不能繼承其他類了。但是如果我們實現(xiàn)Runnable的話,恰好可以解決這個問題。
- 一個類只能extends一個父類,但可以implements多個接口。
- 一個接口則可以同時extends多個接口,卻不能implements任何接口。
3. 使用Callable 和 FutureTask 創(chuàng)建線程
1)創(chuàng)建Callable 接口的實現(xiàn)類,并實現(xiàn) call() 方法,該 call() 方法將作為線程執(zhí)行體,且該 call() 方法有返回值 。
2)創(chuàng)建Callable 實現(xiàn)類的實例,使用 FutureTask 類來包裝 Callable 對象, 該 FutrueTask 對象封裝了該 Callable 對象的 call() 方法的返回值。
3)使用 FutureTask 對象作為 Thread 對象的 target 創(chuàng)建并啟動新線程。
4)調(diào)用FutureTask 對象的 get() 方法來獲得子線程執(zhí)行結(jié)束后的返回值。
public class ThreadDemo03 { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Callable<Object> oneCallable = new Tickets<Object>(); FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable); Thread t = new Thread(oneTask); System.out.println(Thread.currentThread().getName()); t.start(); } } class Tickets<Object> implements Callable<Object>{ //重寫call方法 @Override public Object call() throws Exception { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName()+"-->我是通過實現(xiàn)Callable接口通過FutureTask包裝器來實現(xiàn)的線程"); return null; } }
4. 通過線程池創(chuàng)建線程(使用ExecutorService、Callable、Future實現(xiàn)有返回結(jié)果的多線程。)
ExecutorService、Callable都是屬于Executor框架。返回結(jié)果的線程是在JDK1.5中引入的新特征,還有Future接口也是屬于這個框架,有了這種特征得到返回值就很方便了。
通過分析可以知道,他同樣也是實現(xiàn)了Callable接口,實現(xiàn)了Call方法,所以有返回值。這也就是正好符合了前面所說的兩種分類
執(zhí)行Callable任務(wù)后,可以獲取一個Future的對象,在該對象上調(diào)用get就可以獲取到Callable任務(wù)返回的Object了。get方法是阻塞的,即:線程無返回結(jié)果,get方法會一直等待。
public class ThreadDemo05{ private static int POOL_NUM = 10; //線程池數(shù)量 /** * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub ExecutorService executorService = Executors.newFixedThreadPool(5); for(int i = 0; i<POOL_NUM; i++) { RunnableThread thread = new RunnableThread(); //Thread.sleep(1000); executorService.execute(thread); } //關(guān)閉線程池 executorService.shutdown(); } } class RunnableThread implements Runnable { @Override public void run() { System.out.println("通過線程池方式創(chuàng)建的線程:" + Thread.currentThread().getName() + " "); } }
程序運行結(jié)果:
通過線程池方式創(chuàng)建的線程:pool-1-thread-3
通過線程池方式創(chuàng)建的線程:pool-1-thread-4
通過線程池方式創(chuàng)建的線程:pool-1-thread-1
通過線程池方式創(chuàng)建的線程:pool-1-thread-5
通過線程池方式創(chuàng)建的線程:pool-1-thread-2
通過線程池方式創(chuàng)建的線程:pool-1-thread-5
通過線程池方式創(chuàng)建的線程:pool-1-thread-1
通過線程池方式創(chuàng)建的線程:pool-1-thread-4
通過線程池方式創(chuàng)建的線程:pool-1-thread-3
通過線程池方式創(chuàng)建的線程:pool-1-thread-2
總結(jié)
到此這篇關(guān)于Java基礎(chǔ)之多線程方法狀態(tài)和創(chuàng)建方法的文章就介紹到這了,更多相關(guān)Java多線程方法狀態(tài)和創(chuàng)建內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot配置mybatis駝峰命名規(guī)則自動轉(zhuǎn)換的實現(xiàn)
這篇文章主要介紹了SpringBoot配置mybatis駝峰命名規(guī)則自動轉(zhuǎn)換的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09如何利用IDEA搭建SpringBoot項目整合mybatis實現(xiàn)簡單的登錄功能
這篇文章主要介紹了如何利用IDEA搭建SpringBoot項目整合mybatis實現(xiàn)簡單的登錄功能,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08SpringBoot?Loki安裝簡介及實戰(zhàn)思路
這篇文章主要為大家介紹了SpringBoot?Loki安裝簡介及實戰(zhàn)思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪的相關(guān)資料2022-11-11舉例講解Java編程中this關(guān)鍵字與super關(guān)鍵字的用法
這篇文章主要介紹了Java編程中this關(guān)鍵字與super關(guān)鍵字的用法示例,super是this的父輩,在繼承過程中兩個關(guān)鍵字經(jīng)常被用到,需要的朋友可以參考下2016-03-03Java編程實現(xiàn)統(tǒng)計一個字符串中各個字符出現(xiàn)次數(shù)的方法
這篇文章主要介紹了Java編程實現(xiàn)統(tǒng)計一個字符串中各個字符出現(xiàn)次數(shù)的方法,涉及java針對字符串的遍歷、判斷、運算等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12idea springboot遠(yuǎn)程debug的操作方法
這篇文章主要介紹了idea springboot遠(yuǎn)程debug的操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10