Java併發編程學習一:線程的概念以及使用

該篇文章做爲本身併發學習的一個開始,首先介紹一下線程的概念以及使用。html

討論基於單核cpu進行java

線程的意義

要了解線程的意義,首先先介紹一下進程,什麼是進程?進程概念以下(摘自百度百科):算法

狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。多線程

廣義定義:進程是一個具備必定獨立功能的程序關於某個數據集合的一次運行活動。它是操做系統動態執行的基本單元,在傳統的操做系統中,進程既是基本的分配單元,也是基本的執行單元。併發

簡單的理解就是一個Andorid的App(內部不引入多進程狀況下)應用程序表明的一個進程,該進程在系統中擁有本身的內存空間,全部進程互相獨立,互不影響。經過進程這個抽象的概念,使得操做系統中運行不一樣的應用程序成爲了可能。進程有三個狀態:oracle

  • 運行態/執行態(Running):當一個進程在cpu上運行時,則稱該進程處於運行狀態。
  • 就緒態(Ready):一個進程得到了除處理機外的一切所需資源,一旦獲得cpu便可運行,則稱此進程處於就緒狀態。
  • 阻塞態(Blocked):一個進程正在等待某一事件發生(例如IO操做)而暫時中止運行稱該進程處於阻塞狀態。

進程的狀態在上面三個狀態中轉換,假設系統中總共有3個進程針對單核cpu的狀況,那麼一個時間片內只會有一個進程處於運行態,其餘的進程分別處於就緒態和阻塞態,具體的轉換過程以下圖所示:ide

進程的概念大致介紹到這,那麼既然提出來了進程,爲何又要提出線程的概念呢?試想但一個進程中存在多任務,好比在進程中輸入過程當中須要讀取歷史記錄,同時還要響應用戶的輸入事件,那麼在進程中只能串行的進行,如何進行並行就是線程出現的意義了。學習

線程的概念以下(摘自百度百科):測試

線程,有時被稱爲輕量進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。this

線程是CPU調度的基本單位,在Java中一個進程至少有一個線程,多線程的概念其實是對於系統時間片搶佔的表現。對於單核cpu而言,同一時間內仍是隻能容許一個線程的執行,經過輪流切換使用時間片,達到併發的概念。講到併發,並行跟併發的概念一塊兒記錄一下,用一張圖表示一下兩則的區別:

Erlang 之父 Joe Armstrong提供的一張解釋圖,能夠主觀理解一下。併發指的是一個處理器同時處理多個任務,這種處理方式經過輪流得到時間片方式進行,而並行是併發的子集,並行指的是多個處理器或者是多核的處理器同時處理多個不一樣的任務。

Java中的線程

線程在Java中的實現經過以下三種方式:

  • 繼承Thread,重寫run()方法。
  • 實現Runnable接口。
  • 經過Callable和Future建立。

建立簡單調用Demo以下:

class ThreadTest {

    private Runnable mRunnable = () -> System.out.println("from runnable");

    private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("from Thread");
        }
    }

    private Callable<Void> mCallable = () -> {
        System.out.println("from Callable");
        return null;
    };

    void testThread() {
        new TestThread().run();
        new Thread(mRunnable).run();
        FutureTask<Void> task = new FutureTask<Void>(mCallable);
        new Thread(task).run();
    }
}
-----
輸出結果:
from Thread
from runnable
from Callable

上面經過thread.start()調用啓動了線程,線程在執行過程當中是有生命週期的從出生到死亡,其生命週期以下圖片(來自菜鳥教程)所示:

  • 新建狀態:即對咱們在new一個Thread的時候,此時初始化相關資源,等待start()的調用。
  • 就緒狀態:在調用start()方法後,線程就如就緒狀態,等待cpu進行調用。
  • 運行狀態:在run()方法被調用時說明處於運行狀態,此時線程得到cpu資源進行工做。
  • 阻塞狀態(BLOCKED,WAITING,TIME_WAITING):在運行過程當中線程讓出cpu資源,而且從新進入就緒狀態等待cpu調用。
  • 死亡狀態:線程執行完畢。

關於各個方法對應生命週期的圖以下:

接下來看下線程中的一些重要方法:

注:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,再也不介紹。由於有死鎖傾向。

Thread.sleep(long millis)

Thread.sleep(long millis)使線程從運行狀態轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒爲單位。經過該方法可以使得當前線程暫停執行,讓出cpu資源給其餘的須要的使用,這時候狀態爲BLOCK狀態,必定時間後進入就緒狀態,準備從新獲取cpu。**須要注意的是若是當前線程進入了同步鎖,sleep方法並不會釋放鎖,即便當前線程使用sleep方法讓出了CPU,但其餘被同步鎖擋住了的線程也沒法獲得執行。**在sleep()休眠時間事後,該線程也不必定會立刻執行,這是由於其它線程可能正在運行並且沒有被調度爲放棄執行,除非此線程具備更高的優先級。

這裏須要區分sleep和wait的區別,wait和notify方法跟sychronized關鍵字一塊兒配套使用,wait()方法在進入等待狀態的時候,這個時候會讓度出cpu資源讓其餘線程使用,與sleep()不一樣的是,這個時候wait()方法是不佔有對應的鎖的.

首先看下不使用同步鎖時候的代碼:

void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }
	//輸出結果
2018-11-09 18:52:26 PM : from Thread start run
2018-11-09 18:52:26 PM : from runnable start run
2018-11-09 18:52:26 PM : from runnable start end
2018-11-09 18:52:29 PM : from Thread end

當咱們當用sleep()方法時候確實是會釋放出cpu資源的。那麼繼續確認一下使用同步鎖的狀況:

private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            synchronized (ThreadTest.class) {
                println("from Thread start run");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                println("from Thread end");
            }

        }
    }

    void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }

    private Runnable mRunnable = () -> {
        synchronized (ThreadTest.class) {
            println("from runnable start run");
            println("from runnable start end");
        }
    };
//輸出結果
2018-11-09 18:54:46 PM : from Thread start run
2018-11-09 18:54:49 PM : from Thread end
2018-11-09 18:54:49 PM : from runnable start run
2018-11-09 18:54:49 PM : from runnable start end

上面的輸出結果驗證了咱們的結論的正確性。

####Thread.join(long millis)

join()方法容許一個線程A等待另外一個線程B執行完畢後再執行,也就是說具體使用以下所示:

void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main thread executed");
    }
	
----
//輸出結果
2018-11-09 18:30:14 PM : from Thread start run
2018-11-09 18:30:17 PM : from Thread end
main thread executed

能夠看到主線程會等待TestThread線程執行完畢後在打印出log。主線程在調用thread.join()方法後進入了阻塞狀態,等到thread執行完畢後恢復就緒狀態,等待cpu從新調用。

須要注意的是join()方法實現是經過Object.wait()方法。 當main線程調用thread.join時候,main線程會得到線程對象thread的鎖(wait 意味着拿到該對象的鎖),調用該對象的wait(等待時間),直到該對象喚醒main線程 ,好比退出後。這就意味着main 線程調用thread.join時,必須可以拿到線程thread對象的鎖,詳細可查閱該篇文章

Thread.yield()

該方法的表示該線程讓出cpu資源,**從運行態直接轉換爲就緒態。**通俗的將就是說當一個線程A使用了這個yield()方法以後,它就會把本身CPU執行的時間讓掉,cpu經過調度算法從新讓A或者其它的線程運行。

示例Demo以下:

private class TestThread extends Thread {
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 10; i++) {
                println("TestThread print" + (i + 1) + " start");
                if (i == 3) {
                    yield();
                }
            }
        }
    }

    void testThread() {
        TestThread thread = new TestThread();
        thread.start();
        new Thread(mRunnable).start();
    }

    private Runnable mRunnable = () -> {
        for (int i = 0; i < 10; i++) {
            println("Runnable print" + (i + 1) + " start");
        }
    };
	//輸出結果
2018-11-09 19:36:29 PM : Runnable print1 start
2018-11-09 19:36:29 PM : TestThread print1 start
2018-11-09 19:36:29 PM : Runnable print2 start
2018-11-09 19:36:29 PM : TestThread print2 start
2018-11-09 19:36:29 PM : Runnable print3 start
2018-11-09 19:36:29 PM : TestThread print3 start
2018-11-09 19:36:29 PM : Runnable print4 start
2018-11-09 19:36:29 PM : TestThread print4 start
2018-11-09 19:36:29 PM : Runnable print5 start
2018-11-09 19:36:29 PM : Runnable print6 start
2018-11-09 19:36:29 PM : Runnable print7 start
2018-11-09 19:36:29 PM : Runnable print8 start
2018-11-09 19:36:29 PM : Runnable print9 start
2018-11-09 19:36:29 PM : Runnable print10 start
2018-11-09 19:36:29 PM : TestThread print5 start
2018-11-09 19:36:29 PM : TestThread print6 start
2018-11-09 19:36:29 PM : TestThread print7 start
2018-11-09 19:36:29 PM : TestThread print8 start
2018-11-09 19:36:29 PM : TestThread print9 start
2018-11-09 19:36:29 PM : TestThread print10 start

當TestThread中的i等於3的時候能夠看見確實TestThread讓出cpu讓Runnable中的多執行了一會。

Thread各類interrupt方法

在使用線程的時候,JDK爲咱們提供了中斷線程的操做:interrupt方法,這裏須要注意,經過interrupt方法只是通知線程A應該中斷了,而中斷與否由線程A自主控制。關於Thread中的各類interrupt主要有三個,接下來依次看下

//靜態方法
 public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

 /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

該方法調用當前線程的isInterrupted(boolean ClearInterrupted)方法,isInterrupted(boolean ClearInterrupted)方法可以獲得當前線程的狀態,若是傳遞的是true,那麼在返回當前線程狀態後,它會把當前線程的interrupt狀態「復位」,假設當前線程的isInterrupt狀態爲true,它會返回true,但事後isInterrupt的狀態會復位爲false。

//實例方法
 public boolean isInterrupted() {
        return isInterrupted(false);
    }

該方法爲實例對象調用的方法,與靜態interrupted()方法相同,惟一區別是參數傳遞的是false,即表示直接返回當前線程的狀態

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

interrupt()方法爲線程中斷的方法,經過該方法可以通知對應的線程能夠進入中斷的狀態了。若是線程在調用wait()方法,join()方法,sleep()方法後阻塞,這時候進行調用interrupt()方法,那麼線程會拋出InterruptedException異常;若是線程NIO操做中阻塞,則會拋出ClosedByInterruptException異常(因爲作客戶端,這塊不是特別瞭解,寫錯了麻煩糾正一下)。特別注意一點,調用該方法的必定是阻塞的線程來調用。

當咱們在線程內部中有使用可以拋出InterruptedException異常方法時,官方建議咱們經過主動調用thread.interrupt()方法進行中斷線程的請求;若是線程中沒有,則使用Thread.interrupted()方法以及thread.isInterrupted()方法進行判斷進行中斷請求。複雜的時候能夠混合使用來進行中斷請求的操做。

接下來就是寫代碼驗證的時候了,首先咱們測試一下調用interrupt()`方法:

public static void main(String[] args)  {
        TestThread testThread = new TestThread();
        testThread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testThread.interrupt();
    }
public static class TestThread extends Thread{

        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("interrupt occur");
            }
        }
    }
--------
//輸出結果
interrupt occur

能夠看到,在主線程調用testThread.interrupt()方法後,TestThread拋出了InterruptedException異常,再試試看join()方法:

public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        TestThread1 testThread1 = new TestThread1(testThread);
        testThread.start();
        Thread.sleep(600);
        testThread1.start();
        Thread.sleep(600);
        testThread1.interrupt();
    }
    public static class TestThread1 extends Thread {
        private TestThread mThread;

        public TestThread1(TestThread thread) {
            mThread=thread;
        }
        @Override
        public void run() {
            super.run();
            try {
                mThread.join();
                System.out.println("executed after  TestThread");
            } catch (InterruptedException e) {
                System.out.println("InterruptedException for TestThread");
            }
            System.out.println("do TestThread1 job");
        }
    }
    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
        }
    }
	//輸出結果
	InterruptedException for TestThread
	do TestThread1 job

能夠看到,在testThread1正處於阻塞的時候,咱們調用testThread1.interrupt()後,拋出了InterruptedException 異常,TestThread1沒有等到TestThread執行完畢就直接走本身的任務了。

Ok,接下來再看看interrupted()以及isInterrupted()的驗證過程,這裏寫個死循環來模擬耗時的操做:

public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        Thread.sleep(3000);
        System.out.println("execute testThread.interrupt method");
        testThread.interrupt();
    }

    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            int i=1;
            while (!isInterrupted()) {
                i=2;
            }
            System.out.println("method for is  isInterrupted's value="+isInterrupted());
            System.out.println("method for is  isInterrupted's value2="+isInterrupted());
        }
    }
//結果
execute testThread.interrupt method
method for is  isInterrupted's value=true
method for is  isInterrupted's value2=true

上面的結果能夠看到isInterrupted()方法執行得到到的一直是true,再看看interrupted()方法:

public static void main(String[] args) throws InterruptedException {
        TestThread testThread = new TestThread();
        testThread.start();
        Thread.sleep(3000);
        System.out.println("execute testThread.interrupt method");
        testThread.interrupt();
    }

    public static class TestThread extends Thread {


        @Override
        public void run() {
            super.run();
            int i=1;
            while (!isInterrupted()) {
                i=2;
            }
            System.out.println("method for is  interrupted's value="+Thread.interrupted());
            System.out.println("method for is  interrupted's value2="+Thread.interrupted());
        }
    }
//結果
execute testThread.interrupt method
method for is  interrupted's value=true
method for is  interrupted's value2=false

這裏也符合咱們上面獲得的結論,在調用Thread.interrupted()第一次時候返回了true,java內部把標誌位清除爲了false,第二次再去獲取就變成了false。


參考文章

相關文章
相關標籤/搜索