這是一個關於多線程的系列文章。在接下來的一段時間裏,文章將圍繞多線程進行從淺入深的連載,感興趣的朋友能夠關注一下~java
小A:我們聊完了概念性的東西,是否是要聊一聊實際的用法啦?面試
MDove:OK,接下來咱們正式進入多線程的世界。今天咱們聊一聊基本的使用和一些面試常客的方法。下一篇則重點談一談鎖。編程
MDove:咱們都知道,在Java中開啓多線程。有兩種手段:一種是繼續Thread類;另一種是實現Runable接口。(固然還能夠實現Callable、Future等方式。)api
小A:那繼承Thread和實現Runable有什麼不一樣麼?安全
MDove:從技術角度上來講並無不一樣,最大的不一樣應該算是設計上。由於咱們都知道Java是單繼承,因此當你繼承了Thread勢必不能繼承其餘類。所以implement接口能夠實現「多繼承」的效果。多線程
小A:那這倆種方式是怎樣寫的呢?ide
MDove:學習
1.繼承 Thread 類this
public class MyThread extends Thread {
@Override
public void run() {
//TODO
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
複製代碼
2.實現 Runnable 接口spa
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()這個方法,不過已經不推薦使用了。比較正確的中止線程的幾種方法是:
一、使用退出標誌,使線程正常退出,也就是當run()方法完成後線程中止。
二、使用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部:
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思想的具體實現....
MDove:接下來,咱們聊一聊一些基礎的api的做用: ####sleep() 方法: sleep() 容許 指定以毫秒爲單位的一段時間做爲參數,它使得線程在指定的時間內進入阻塞狀態,不能獲得CPU 時間,指定的時間一過,線程從新進入可執行狀態。(不釋放鎖)
suspend()使得線程進入阻塞狀態,而且不會自動恢復,必須其對應的resume() 被調用,才能使得線程從新進入可執行狀態。典型地,suspend() 和 resume() 被用在等待另外一個線程產生的結果的情形。
Thread 類中的方法;不會釋放鎖;可在任何位置調用。
yield() 使得線程放棄當前分得的 CPU 時間,可是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。
兩個方法配套使用,wait() 使得線程進入阻塞狀態,它有兩種形式,一種容許 指定以毫秒爲單位的一段時間做爲參數,另外一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程從新進入可執行狀態,後者則必須對應的 notify() 被調用。
Object中的方法; 會釋放鎖;這一對方法卻必須在 synchronized 方法或塊中調用。緣由也很好理解:只有在synchronized這類同步代碼塊中,當前線程才佔有鎖,纔有鎖能夠釋放。同理,調用方法的對象上的鎖必須爲當前線程所擁有,這樣纔有鎖能夠釋放。所以,這一對方法調用必須放置在synchronized這樣的同步代碼中。注意:若不知足這一條件,仍能編譯,但在運行時會出現IllegalMonitorStateException異常。
不要覺得它是中斷某個線程!它只是線線程發送一箇中斷信號,讓線程在無限等待時(如死鎖時)能拋出拋出,從而結束線程,可是若是你吃掉了這個異常,那麼這個線程仍是不會中斷的!
MDove:這些方法可須要好好的去體會呦。不光是面試中經常遇到,更可能是它們是咱們操做Thread的利器。
小A:感受線程還能夠,不是很難。
MDove:那是由於咱們目前纔是最簡單的使用階段。如何高效、安全的使用線程,可不是隻會使用api就夠,既然synchronized能夠知足線程安全,那麼爲何還須要其餘花裏胡哨的各類各樣的其餘同步方式。因此多線程編程須要長久的經驗支撐。我們接下來的內容會一點點深刻,但願你能夠一直以爲不難呦~~
小A:嘿嘿嘿,火燒眉毛了~