前言
這是一個關於多執行緒的系列文章。在接下來的一段時間裡,文章將圍繞多執行緒進行從淺入深的連載,感興趣的朋友可以關注一下~
正文
小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:嘿嘿嘿,迫不及待了~