[從零開啟 Java 多執行緒 - 1 ]:開胃小菜
這是一個關於多執行緒的系列文章。在接下來的一段時間裡,文章將圍繞多執行緒進行從淺入深的連載,感興趣的朋友可以關注一下~
正文
小A:咱們聊完了概念性的東西,是不是要聊一聊實際的用法啦?
MDove:OK,接下來我們正式進入多執行緒的世界。今天我們聊一聊基本的使用和一些面試常客的方法。下一篇則重點談一談鎖。
MDove:我們都知道,在Java中開啟多執行緒。有兩種手段:一種是繼續Thread類;另外一種是實現Runable介面。(當然還可以實現Callable、Future等方式。)
小A:那繼承Thread和實現Runable有什麼不同麼?
MDove:從技術角度上來說並沒有不同,最大的不同應該算是設計上。因為我們都知道Java是單繼承,所以當你繼承了Thread勢必不能繼承其他類。因此implement介面可以實現“多繼承”的效果。
小A:那這倆種方式是怎樣寫的呢?
MDove:
1.繼承 Thread 類
public class MyThread extends Thread { @Override public void run() { //TODO } } public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } 複製程式碼
2.實現 Runnable 介面
public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { //TODO } }).start(); } 複製程式碼
MDove:呼叫start()就標誌著執行緒的開啟,但是start()方法的呼叫後並不是立即執行多執行緒程式碼,而是使得該執行緒變為可執行態(Runnable),什麼時候執行是由作業系統決定的。
MDove:另外需要注意一點,start()不應該被重複呼叫,否則會出現java.lang.IllegalThreadStateException異常。
小A:start執行緒我會了?那停止執行緒呢?是stop麼?
停止執行緒
MDove:的確有stop()這個方法,不過已經不推薦使用了。比較正確的停止執行緒的幾種方法是:
1、使用退出標誌,使執行緒正常退出,也就是當run()方法完成後執行緒停止。
2、使用interrupt()方法中斷執行緒。
MDove:簡單寫一個思路2的demo,你應該能夠看明白:
public class MyThread extends Thread { @Override public void run() { try { for (int i=0; i<50000; i++){ if (this.isInterrupted()) { System.out.println("已經是停止狀態了!"); throw new InterruptedException(); } System.out.println(i); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { MyThread myThread =new MyThread(); myThread.start(); Thread.sleep(100); myThread.interrupt(); } } 複製程式碼
小A:通過Thread的interrupt()方法,來通知執行緒停止。然後我們通過isInterrupted()判斷是否停止執行緒,然後使用拋異常的方式停止執行緒?
MDove:沒錯,但是不止拋異常,return,break都可以滿足這個要求。
小A:OK,停止執行緒我明白了,我記得上篇文章,你用了大量的篇幅去聊執行緒安全的問題,那麼在程式碼中,我們應該怎麼做呢?
執行緒安全
MDove:OK,讓我們先模擬一個簡單的不安全的執行緒demo:
public class MyThread implements Runnable { private int count = 5; @Override public void run() { fun(); } private void fun() { count--; System.out.println("執行緒:" + Thread.currentThread().getName() + "開始計算,計算結果count = " + count); } } 複製程式碼
public static void main(String[] args) { MyThread myThread = new MyThread(); Thread thread1 = new Thread(myThread,"執行緒1"); Thread thread2 = new Thread(myThread,"執行緒2"); Thread thread3 = new Thread(myThread,"執行緒3"); Thread thread4 = new Thread(myThread,"執行緒4"); Thread thread5 = new Thread(myThread,"執行緒5"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } 複製程式碼
MDove:結果不需多言,我相信有了上次文章的鋪墊,這個demo的問題你也能看出來吧?
小A:是結果有重複麼?沒辦法順序的執行到0?
MDove:沒錯,這裡出現的問題就是原子性的問題。因為自加或者是自減操作,真正成為指令時並非一個指令,而是3部:
- 1、取值
- 2、計算
- 3、賦值
MDove:因此,在這三個步驟中,如果有多個執行緒同時訪問,那麼一定會出現非執行緒安全問題。
小A:那如何解決這個問題呢?
MDove:最直接也是最簡單的方法,使用synchronized同步關鍵字:
private synchronized void fun() { count--; System.out.println("執行緒:" + Thread.currentThread().getName() + "開始計算,計算結果count = " + count); } 複製程式碼
列印結果: 執行緒2 計算 count = 4 執行緒3 計算 count = 3 執行緒1 計算 count = 2 執行緒4 計算 count = 1 執行緒5 計算 count = 0
MDove:當然我們也可以使用Lock:
ReentrantLock lock=new ReentrantLock(); private void fun() { lock.lock(); try { count--; System.out.print("執行緒:" + Thread.currentThread().getName() + "開始計算,計算結果count = " + count); } finally { lock.unlock(); } } 複製程式碼
小A:那這倆者有什麼不同呢?
MDove:不要著急,下一篇文章。再讓我從位元組碼層面,好好得給你捋一捋它們的不同。當然這個題目還有其他的解法,比如volatile、AtomicInteger等手段保證可見性、原子性、有序性。
小A:volatile、AtomicInteger又是啥?
MDove:不要著急,接下來文章會好好針對volatile進行總結,畢竟是面試的常客。當然AtomicInteger也頗為重要,因為它是CAS思想的具體實現....
面試常客API
MDove:接下來,我們聊一聊一些基礎的api的作用: ####sleep() 方法: sleep() 允許 指定以毫秒為單位的一段時間作為引數,它使得執行緒在指定的時間內進入阻塞狀態,不能得到CPU 時間,指定的時間一過,執行緒重新進入可執行狀態。(不釋放鎖)
suspend() 和 resume() 方法:
suspend()使得執行緒進入阻塞狀態,並且不會自動恢復,必須其對應的resume() 被呼叫,才能使得執行緒重新進入可執行狀態。典型地,suspend() 和 resume() 被用在等待另一個執行緒產生的結果的情形。
Thread 類中的方法;不會釋放鎖;可在任何位置呼叫。
yield() 方法:
yield() 使得執行緒放棄當前分得的 CPU 時間,但是不使執行緒阻塞,即執行緒仍處於可執行狀態,隨時可能再次分得 CPU 時間。
wait() 和 notify() 方法():
兩個方法配套使用,wait() 使得執行緒進入阻塞狀態,它有兩種形式,一種允許 指定以毫秒為單位的一段時間作為引數,另一種沒有引數,前者當對應的 notify() 被呼叫或者超出指定時間時執行緒重新進入可執行狀態,後者則必須對應的 notify() 被呼叫。
Object中的方法; 會釋放鎖;這一對方法卻必須在 synchronized 方法或塊中呼叫。原因也很好理解 :只有在synchronized這類同步程式碼塊中,當前執行緒才佔有鎖,才有鎖可以釋放。同理,呼叫方法的物件上的鎖必須為當前執行緒所擁有,這樣才有鎖可以釋放。因此,這一對方法呼叫必須放置在synchronized這樣的同步程式碼中。注意:若不滿足這一條件,仍能編譯,但在執行時會出現IllegalMonitorStateException異常。
interrupt():
不要以為它是中斷某個執行緒!它只是線執行緒傳送一箇中斷訊號,讓執行緒在無限等待時(如死鎖時)能丟擲丟擲,從而結束執行緒,但是如果你吃掉了這個異常,那麼這個執行緒還是不會中斷的!
MDove:這些方法可需要好好的去體會呦。不光是面試中常常遇到,更多是它們是我們操作Thread的利器。
小A:感覺執行緒還可以,不是很難。
MDove:那是因為我們目前才是最簡單的使用階段。如何高效、安全的使用執行緒,可不是隻會使用api就夠,既然synchronized可以滿足執行緒安全,那麼為什麼還需要其他花裡胡哨的各種各樣的其他同步方式。所以多執行緒程式設計需要長久的經驗支撐。咱們接下來的內容會一點點深入,希望你可以一直覺得不難呦~~
小A:嘿嘿嘿,迫不及待了~