新建線程很簡單。只須要使用new關鍵字建立一個線程對象,而後調用它的start()啓動線程便可。java
Thread thread1 = new Thread1(); t1.start();
那麼線程start()以後,會幹什麼呢?線程有個run()方法,start()會建立一個新的線程並讓這個線程執行run()方法。微信
這裏須要注意,下面代碼也能經過編譯,也能正常執行。可是,卻不能新建一個線程,而是在當前線程中調用run()方法,將run方法只是做爲一個普通的方法調用。多線程
Thread thread = new Thread1(); thread1.run();
因此,但願你們注意,調用start方法和直接調用run方法的區別。併發
start方法是啓動一個線程,run方法只會在當前線程中串行的執行run方法中的代碼。eclipse
默認狀況下, 線程的run方法什麼都沒有,啓動一個線程以後立刻就結束了,因此若是你須要線程作點什麼,須要把您的代碼寫到run方法中,因此必須重寫run方法。異步
Thread thread1 = new Thread() { @Override public void run() { System.out.println("hello,我是一個線程!"); } }; thread1.start();
上面是使用匿名內部類實現的,重寫了Thread的run方法,而且打印了一條信息。咱們能夠經過繼承Thread類,而後重寫run方法,來自定義一個線程。但考慮java是單繼承的,從擴展性上來講,咱們實現一個接口來自定義一個線程更好一些,java中恰好提供了Runnable接口來自定義一個線程。分佈式
@FunctionalInterface public interface Runnable { public abstract void run(); }
Thread類有一個很是重要的構造方法:ide
public Thread(Runnable target)
咱們在看一下Thread的run方法:高併發
public void run() { if (target != null) { target.run(); } }
當咱們啓動線程的start方法以後,線程會執行run方法,run方法中會調用Thread構造方法傳入的target的run方法。大數據
實現Runnable接口是比較常見的作法,也是推薦的作法。
通常來講線程執行完畢就會結束,無需手動關閉。可是若是咱們想關閉一個正在運行的線程,有什麼方法呢?能夠看一下Thread類中提供了一個stop()方法,調用這個方法,就能夠當即將一個線程終止,很是方便。
package com.itsoku.chat01; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公衆號:路人甲Java,專一於java技術分享(帶你玩轉 爬蟲、分佈式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注! */ @Slf4j public class Demo01 { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { log.info("start"); boolean flag = true; while (flag) { ; } log.info("end"); } }; thread1.setName("thread1"); thread1.start(); //當前線程休眠1秒 TimeUnit.SECONDS.sleep(1); //關閉線程thread1 thread1.stop(); //輸出線程thread1的狀態 log.info("{}", thread1.getState()); //當前線程休眠1秒 TimeUnit.SECONDS.sleep(1); //輸出線程thread1的狀態 log.info("{}", thread1.getState()); } }
運行代碼,輸出:
18:02:15.312 [thread1] INFO com.itsoku.chat01.Demo01 - start 18:02:16.311 [main] INFO com.itsoku.chat01.Demo01 - RUNNABLE 18:02:17.313 [main] INFO com.itsoku.chat01.Demo01 - TERMINATED
代碼中有個死循環,調用stop方法以後,線程thread1的狀態變爲TERMINATED(結束狀態),線程中止了。
咱們使用idea或者eclipse的時候,會發現這個方法是一個廢棄的方法,也就是說,在未來,jdk可能就會移除該方法。
stop方法爲什麼會被廢棄而不推薦使用?stop方法過於暴力,強制把正在執行的方法中止了。
你們是否遇到過這樣的場景:電力系統須要維修,此時我們正在寫代碼,維修人員直接將電源關閉了,代碼還沒保存的,是否是很崩潰,這種方式就像直接調用線程的stop方法相似。線程正在運行過程當中,被強制結束了,可能會致使一些意想不到的後果。能夠給你們發送一個通知,告訴你們保存一下手頭的工做,將電腦關閉。
在java中,線程中斷是一種重要的線程寫做機制,從表面上理解,中斷就是讓目標線程中止執行的意思,實際上並不是徹底如此。在上面中,咱們已經詳細討論了stop方法中止線程的壞處,jdk中提供了更好的中斷線程的方法。嚴格的說,線程中斷並不會使線程當即退出,而是給線程發送一個通知,告知目標線程,有人但願你退出了!至於目標線程接收到通知以後如何處理,則徹底由目標線程本身決定,這點很重要,若是中斷後,線程當即無條件退出,咱們又會到stop方法的老問題。
Thread提供了3個與線程中斷有關的方法,這3個方法容易混淆,你們注意下:
public void interrupt() //中斷線程 public boolean isInterrupted() //判斷線程是否被中斷 public static boolean interrupted() //判斷線程是否被中斷,並清除當前中斷狀態
interrupt()方法是一個實例方法,它通知目標線程中斷,也就是設置中斷標誌位爲true,中斷標誌位表示當前線程已經被中斷了。isInterrupted()方法也是一個實例方法,它判斷當前線程是否被中斷(經過檢查中斷標誌位)。最後一個方法interrupted()是一個靜態方法,返回boolean類型,也是用來判斷當前線程是否被中斷,可是同時會清除當前線程的中斷標誌位的狀態。
while (true) { if (this.isInterrupted()) { System.out.println("我要退出了!"); break; } } } }; thread1.setName("thread1"); thread1.start(); TimeUnit.SECONDS.sleep(1); thread1.interrupt();
上面代碼中有個死循環,interrupt()方法被調用以後,線程的中斷標誌將被置爲true,循環體中經過檢查線程的中斷標誌是否爲ture(this.isInterrupted()
)來判斷線程是否須要退出了。
再看一種中斷的方法:
static volatile boolean isStop = false; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { while (true) { if (isStop) { System.out.println("我要退出了!"); break; } } } }; thread1.setName("thread1"); thread1.start(); TimeUnit.SECONDS.sleep(1); isStop = true; }
代碼中經過一個變量isStop來控制線程是否中止。
經過變量控制和線程自帶的interrupt方法來中斷線程有什麼區別呢?
若是一個線程調用了sleep方法,一直處於休眠狀態,經過變量控制,還能夠中斷線程麼?你們能夠思考一下。
此時只能使用線程提供的interrupt方法來中斷線程了。
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { while (true) { //休眠100秒 try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我要退出了!"); break; } } }; thread1.setName("thread1"); thread1.start(); TimeUnit.SECONDS.sleep(1); thread1.interrupt(); }
調用interrupt()方法以後,線程的sleep方法將會拋出InterruptedException
異常。
Thread thread1 = new Thread() { @Override public void run() { while (true) { //休眠100秒 try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (this.isInterrupted()) { System.out.println("我要退出了!"); break; } } } };
運行上面的代碼,發現程序沒法終止。爲何?
代碼須要改成:
Thread thread1 = new Thread() { @Override public void run() { while (true) { //休眠100秒 try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { this.interrupt(); e.printStackTrace(); } if (this.isInterrupted()) { System.out.println("我要退出了!"); break; } } } };
上面代碼能夠終止。
注意:sleep方法因爲中斷而拋出異常以後,線程的中斷標誌會被清除(置爲false),因此在異常中須要執行this.interrupt()方法,將中斷標誌位置爲true
爲了支持多線程之間的協做,JDK提供了兩個很是重要的方法:等待wait()方法和通知notify()方法。這2個方法並非在Thread類中的,而是在Object類中定義的。這意味着全部的對象均可以調用者兩個方法。
public final void wait() throws InterruptedException; public final native void notify();
當在一個對象實例上調用wait()方法後,當前線程就會在這個對象上等待。這是什麼意思?好比在線程A中,調用了obj.wait()方法,那麼線程A就會中止繼續執行,轉爲等待狀態。等待到何時結束呢?線程A會一直等到其餘線程調用obj.notify()方法爲止,這時,obj對象成爲了多個線程之間的有效通訊手段。
那麼wait()方法和notify()方法是如何工做的呢?如圖2.5展現了二者的工做過程。若是一個線程調用了object.wait()方法,那麼它就會進出object對象的等待隊列。這個隊列中,可能會有多個線程,由於系統可能運行多個線程同時等待某一個對象。當object.notify()方法被調用時,它就會從這個隊列中隨機選擇一個線程,並將其喚醒。這裏但願你們注意一下,這個選擇是不公平的,並非先等待線程就會優先被選擇,這個選擇徹底是隨機的。
除notify()方法外,Object獨享還有一個nofiyAll()方法,它和notify()方法的功能相似,不一樣的是,它會喚醒在這個等待隊列中全部等待的線程,而不是隨機選擇一個。
這裏強調一點,Object.wait()方法並不能隨便調用。它必須包含在對應的synchronize語句彙總,不管是wait()方法或者notify()方法都須要首先獲取目標獨享的一個監視器。圖2.6顯示了wait()方法和nofiy()方法的工做流程細節。其中T1和T2表示兩個線程。T1在正確執行wait()方法錢,必須得到object對象的監視器。而wait()方法在執行後,會釋放這個監視器。這樣作的目的是使其餘等待在object對象上的線程不至於由於T1的休眠而所有沒法正常執行。
線程T2在notify()方法調用前,也必須得到object對象的監視器。所幸,此時T1已經釋放了這個監視器,所以,T2能夠順利得到object對象的監視器。接着,T2執行了notify()方法嘗試喚醒一個等待線程,這裏假設喚醒了T1。T1在被喚醒後,要作的第一件事並非執行後續代碼,而是要嘗試從新得到object對象的監視器,而這個監視器也正是T1在wait()方法執行前所持有的那個。若是暫時沒法得到,則T1還必須等待這個監視器。當監視器順利得到後,T1才能夠在真正意義上繼續執行。
給你們上個例子:
package com.itsoku.chat01; /** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公衆號:路人甲Java,專一於java技術分享(帶你玩轉 爬蟲、分佈式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注! */ public class Demo06 { static Object object = new Object(); public static class T1 extends Thread { @Override public void run() { synchronized (object) { System.out.println(System.currentTimeMillis() + ":T1 start!"); try { System.out.println(System.currentTimeMillis() + ":T1 wait for object"); object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ":T1 end!"); } } } public static class T2 extends Thread { @Override public void run() { synchronized (object) { System.out.println(System.currentTimeMillis() + ":T2 start,notify one thread! "); object.notify(); System.out.println(System.currentTimeMillis() + ":T2 end!"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { new T1().start(); new T2().start(); } }
運行結果:
1562934497212:T1 start! 1562934497212:T1 wait for object 1562934497212:T2 start,notify one thread! 1562934497212:T2 end! 1562934499213:T1 end!
注意下打印結果,T2調用notify方法以後,T1並不能當即繼續執行,而是要等待T2釋放objec投遞鎖以後,T1從新成功獲取鎖後,才能繼續執行。所以最後2行日誌相差了2秒(由於T2調用notify方法後休眠了2秒)。
注意:Object.wait()方法和Thread.sleeep()方法均可以讓現場等待若干時間。除wait()方法能夠被喚醒外,另一個主要的區別就是wait()方法會釋放目標對象的鎖,而Thread.sleep()方法不會釋放鎖。
再給你們講解一下wait(),notify(),notifyAll(),加深一下理解:
能夠這麼理解,obj對象上有2個隊列,如圖1,q1:等待隊列,q2:準備獲取鎖的隊列;兩個隊列都爲空。
obj.wait()過程:
synchronize(obj){ obj.wait(); }
假若有3個線程,t一、t二、t3同時執行上面代碼,t一、t二、t3會進入q2隊列,如圖2,進入q2的隊列的這些線程纔有資格去爭搶obj的鎖,假設t1爭搶到了,那麼t二、t3機型在q2中等待着獲取鎖,t1進入代碼塊執行wait()方法,此時t1會進入q1隊列,而後系統會通知q2隊列中的t二、t3去爭搶obj的鎖,搶到以後過程如t1的過程。最後t一、t二、t3都進入了q1隊列,如圖3。
上面過程以後,又來了線程t4執行了notify()方法,以下:**
synchronize(obj){ obj.notify(); }
t4會獲取到obj的鎖,而後執行notify()方法,系統會從q1隊列中隨機取一個線程,將其加入到q2隊列,假如t2運氣比較好,被隨機到了,而後t2進入了q2隊列,如圖4,進入q2的隊列的鎖纔有資格爭搶obj的鎖,t4線程執行完畢以後,會釋放obj的鎖,此時隊列q2中的t2會獲取到obj的鎖,而後繼續執行,執行完畢以後,q1中包含t一、t3,q2隊列爲空,如圖5
接着又來了個t5隊列,執行了notifyAll()方法,以下:
synchronize(obj){ obj.notifyAll(); }
2.調用obj.wait()方法,當前線程會加入隊列queue1,而後會釋放obj對象的鎖
t5會獲取到obj的鎖,而後執行notifyAll()方法,系統會將隊列q1中的線程都移到q2中,如圖6,t5線程執行完畢以後,會釋放obj的鎖,此時隊列q2中的t一、t3會爭搶obj的鎖,爭搶到的繼續執行,未加強到的帶鎖釋放以後,系統會通知q2中的線程繼續爭搶索,而後繼續執行,最後兩個隊列中都爲空了。
Thread類中還有2個方法,即線程掛起(suspend)和繼續執行(resume),這2個操做是一對相反的操做,被掛起的線程,必需要等到resume()方法操做後,才能繼續執行。系統中已經標註着2個方法過期了,不推薦使用。
系統不推薦使用suspend()方法去掛起線程是由於suspend()方法致使線程暫停的同時,並不會釋聽任何鎖資源。此時,其餘任何線程想要訪問被它佔用的鎖時,都會被牽連,致使沒法正常運行(如圖2.7所示)。直到在對應的線程上進行了resume()方法操做,被掛起的線程才能繼續,從而其餘全部阻塞在相關鎖上的線程也能夠繼續執行。可是,若是resume()方法操做意外地在suspend()方法前就被執行了,那麼被掛起的線程可能很難有機會被繼續執行了。而且,更嚴重的是:它所佔用的鎖不會被釋放,所以可能會致使整個系統工做不正常。並且,對於被掛起的線程,從它線程的狀態上看,竟然仍是Runnable狀態,這也會影響咱們隊系統當前狀態的判斷。
上個例子:
/** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公衆號:路人甲Java,專一於java技術分享(帶你玩轉 爬蟲、分佈式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注! */ public class Demo07 { static Object object = new Object(); public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { synchronized (object) { System.out.println("in " + this.getName()); Thread.currentThread().suspend(); } } } public static void main(String[] args) throws InterruptedException { T1 t1 = new T1("t1"); t1.start(); Thread.sleep(100); T1 t2 = new T1("t2"); t2.start(); t1.resume(); t2.resume(); t1.join(); t2.join(); } }
運行代碼輸出:
in t1 in t2
咱們會發現程序不會結束,線程t2被掛起了,致使程序沒法結束,使用jstack命令查看線程堆棧信息能夠看到:
"t2" #13 prio=5 os_prio=0 tid=0x000000002796c000 nid=0xa3c runnable [0x000000002867f000] java.lang.Thread.State: RUNNABLE at java.lang.Thread.suspend0(Native Method) at java.lang.Thread.suspend(Thread.java:1029) at com.itsoku.chat01.Demo07$T1.run(Demo07.java:20) - locked <0x0000000717372fc0> (a java.lang.Object)
發現t2線程在suspend0處被掛起了,t2的狀態居然仍是RUNNABLE狀態,線程明明被掛起了,狀態仍是運行中容易致使咱們隊當前系統進行誤判,代碼中已經調用resume()方法了,可是因爲時間前後順序的緣故,resume並無生效,這致使了t2永遠滴被掛起了,而且永遠佔用了object的鎖,這對於系統來講多是致命的。
不少時候,一個線程的輸入可能很是依賴於另一個或者多個線程的輸出,此時,這個線程就須要等待依賴的線程執行完畢,才能繼續執行。jdk提供了join()操做來實現這個功能。以下所示,顯示了2個join()方法:
public final void join() throws InterruptedException; public final synchronized void join(long millis) throws InterruptedException;
第1個方法表示無限等待,它會一直只是當前線程。知道目標線程執行完畢。
第2個方法有個參數,用於指定等待時間,若是超過了給定的時間目標線程還在執行,當前線程也會中止等待,而繼續往下執行。
好比:線程T1須要等待T二、T3完成以後才能繼續執行,那麼在T1線程中須要分別調用T2和T3的join()方法。
上個示例:
/** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公衆號:路人甲Java,專一於java技術分享(帶你玩轉 爬蟲、分佈式事務、異步消息服務、任務調度、分庫分表、大數據等),喜歡請關注! */ public class Demo08 { static int num = 0; public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println(System.currentTimeMillis() + ",start " + this.getName()); for (int i = 0; i < 10; i++) { num++; try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(System.currentTimeMillis() + ",end " + this.getName()); } } public static void main(String[] args) throws InterruptedException { T1 t1 = new T1("t1"); t1.start(); t1.join(); System.out.println(System.currentTimeMillis() + ",num = " + num); } }
執行結果:
1562939889129,start t1 1562939891134,end t1 1562939891134,num = 10
num的結果爲10,一、3行的時間戳相差2秒左右,說明主線程等待t1完成以後才繼續執行的。
看一下jdk1.8中Thread.join()方法的實現:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
從join的代碼中能夠看出,在被等待的線程上使用了synchronize,調用了它的wait()方法,線程最後執行完畢以後,系統會自動調用它的notifyAll()方法,喚醒全部在此線程上等待的其餘線程。
注意:被等待的線程執行完畢以後,系統自動會調用該線程的notifyAll()方法。因此通常狀況下,咱們不要去在線程對象上使用wait()、notify()、notifyAll()方法。
另一個方法是Thread.yield(),他的定義以下:
public static native void yield();
yield是謙讓的意思,這是一個靜態方法,一旦執行,它會讓當前線程出讓CPU,但須要注意的是,出讓CPU並非說不讓當前線程執行了,當前線程在出讓CPU後,還會進行CPU資源的爭奪,可是可否再搶到CPU的執行權就不必定了。所以,對Thread.yield()方法的調用好像就是在說:我已經完成了一些主要的工做,我能夠休息一下了,可讓CPU給其餘線程一些工做機會了。
若是以爲一個線程不過重要,或者優先級比較低,而又擔憂此線程會過多的佔用CPU資源,那麼能夠在適當的時候調用一下Thread.yield()方法,給與其餘線程更多的機會。
java高併發系列交流羣