[從零開啓 Java 多線程 - 1 ]:開胃小菜

前言

這是一個關於多線程的系列文章。在接下來的一段時間裏,文章將圍繞多線程進行從淺入深的連載,感興趣的朋友能夠關注一下~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思想的具體實現....

面試常客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:嘿嘿嘿,火燒眉毛了~

劇終

我是一個應屆生,最近和朋友們維護了一個公衆號,內容是咱們在從應屆生過渡到開發這一路所踩過的坑,以及咱們一步步學習的記錄,若是感興趣的朋友能夠關注一下,一同加油~

我的公衆號:IT面試填坑小分隊
相關文章
相關標籤/搜索