java 多線程基礎學習

一、線程概念

  1. 進程:程序運行資源分配的最小單位,每個進程都有自己獨立的代碼和數據空間,操作系統為進程分配各種資源。
  2. 線程:CPU調度的最小單位,也叫輕量級進程,每個線程都有各自的堆棧、計數器和局部變量等屬性。
  3. 線程和進程關系:線程依賴于進程而存在,多個線程共享進程的內存空間。

異步和同步:

  • 同步:在同一個線程中執行一段業務邏輯時,按順序執行,在前面的結果沒有返回時,后面的程序就不能往下執行,必須等待前一個結果返回時后面的才能往下執行。
  • 異步:多線程是實現異步的一個手段,異步是當一個請求發送給被調用者,調用者可以不用等待結果返回而可以做其他的事情。

就像我們每天去到公司一樣,先打開電腦,在電腦開機過程中可以去接點水喝,而不用等待電腦開機再去接水喝。

并發和并行

  • 并行:同一時刻可以處理事情的能力,比如一臺四核的電腦,可以同時運行四個任務,我們就說這臺電腦并行度是4
  • 并發:在單位時間內可以處理的事情,主要還是看這臺電腦時間分片的長短,如果這臺電腦的時間分片為100ms,在1s內就可以處理10個任務,那么就說這臺電腦的并發度是40。因為任務是在交替執行的,并發的任務就會讓我們以為這些任務是同時執行的,其實還是順序執行的。

二、線程的優勢

現如今服務器多采用多處理器,CPU的基本調度單位是線程,如果一個程序只有一個線程的話,那么就只能發揮一個CPU的作用,其他CPU的資源將會閑置,這在很大程度上浪費了CPU的資源,如果能夠多個CPU同時發揮作用,在設計正確的情況下,可以通過提高CPU資源的利用率來提高系統的吞吐率。

三、線程狀態

通過Thread.state 可以查看線程的狀態:

  1. NEW:新建狀態。這種狀態下線程還沒有開始,也就是還沒有調用start方法
  2. RUNNABLE:可運行狀態。這個狀態下的線程可能處于執行階段,但是也有可能在等待來自操作系統的其他資源,例如等待CPU為其分配時間片。ready和running在這兩種狀態在Java中統稱為RUNNABLE,分開寫是為了更好的理解
  3. BLOCKED:阻塞狀態。表示線程阻塞于鎖
  4. WAITING:等待狀態。進入等待狀態的線程需要其他線程做出一些特定動作,例如通知或中斷
  5. TIMED_WAITING:超時等待狀態。該狀態不同于WAITING狀態,它可以在指定時間內自動返回。
  6. TERMINATED:終止狀態。表示當前線程已經執行結束
image

t.start()之后并不代表線程已經啟動,此時它只是在可運行池中,隨時等待被CPU調度,一旦獲取到CPU時間片才真正的處于可運行狀態。

線程的優先級:

1、線程有1-10,10個優先級選擇,最小優先級是1(MIN_PRIORITY),默認優先級5(NORM_PRIORITY),

最大優先級是10(MAX_PRIORITY)


    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

2、如果沒有指定線程的優先級,默認是普通優先級,也就是NORM_PRIORITY

3、通過t.setPriority(int newPriority)為線程設定優先級

注意:不要試圖通過設定優先級來指定線程的啟動順序,優先級的設定只是說明這個線程在CPU調度時有更大的概率被選中,而且這種優先級設定有不確定性,在某些操作系統中可能優先級的設定并不起作用。

方法介紹

  • join():在線程A中調用B的join()方法,線程B將會先于線程A執行,線程A必須等待線程B執行完成才能執行,有點類似于插隊。同時join()方法還有兩個重載的帶有時間的方法,join(long millis)、join(long millis, int nanos),它表示如果線程B沒有在指定時間內完成,則返回。相當于給插隊線程加了時間限制,超時自動返回。
  • yield():Thread的靜態方法。當前線程讓出CPU執行權,當前線程會從運行狀態變為可運行狀態,重新回到可運行線程池,但是他還是會可能被再次選中執行。
  • sleep(long millis):Thread的靜態方法,使當前線程t休眠n毫秒,如果當前線程t持有鎖,休眠期間不會釋放鎖,其他線程將無法獲得鎖,當其他線程調用t.interrupt()喚醒休眠線程。當到了休眠時間當前線程t會自動進入RUNNABLE狀態。
  • wait():對象方法,使當前線程進入等待狀態,它不會返回,除非我們調用t.notify()或者t.notifyAll()方法喚醒,喚醒之后將和其他線程競爭獲得鎖。調用wait()方法的前提是當前線程必須持有鎖,否則會拋異常,在調用wait()方法之后會釋放鎖,讓其他線程有機會獲得鎖。

sleep()和wait()的區別:

1、sleep()是Thread的靜態方法,wait()是Object的方法。

2、當調用sleep()的線程獲得鎖時不會自動釋放鎖,調用wait()會釋放鎖。

3、sleep()方法需要其他線程調用當前線程的interrupt()方法或者時間過期才能喚醒,wait()方法需要調用t.notify()或者t.notifyAll()方法喚醒。

線程的中斷:

如果線程執行完成或者拋出未處理的異常,線程就會終止。

線程中斷的方法有stop()、resume()、suspend()和interrupt(),但是前三個已經廢棄了,stop()會使線程不正確釋放資源,resume()只是為了suspend()而存在,這兩個方法會導致死鎖。所以最后只剩下interrupt()了。

java線程是協作式的,意思就是當調用當前線程t的interrupt()時,當前線程t并不會立馬終止,而是跟當前線程打個招呼,“兄弟,你死期到了,我跟你說一下,你愛死不死”,同時會把t的中斷標志設置為true。當線程拋出InterruptedException時,線程中斷標志將會清除,調用靜態方法Thread.interrupted()可以判斷線程是否中斷,同時將中斷標志將被清除并設置為false。

實例如下:

主函數

public static void main(String[] args) throws InterruptedException {
    //創建兩個線程
    InterruptThread interruptThread = new InterruptThread();
    interruptThread.setName("thread1");

    InterruptThread2 interruptThread2 = new InterruptThread2();
    interruptThread2.setName("thread2");

    //啟動兩個線程
    interruptThread.start();
    interruptThread2.start();

    //主線程休眠5秒 使兩個線程充分運行
    TimeUnit.SECONDS.sleep(5);

    //中斷InterruptThread
    interruptThread.interrupt();

}

靜態內部線程1:

private static class InterruptThread extends Thread{
    @Override
    public void run() {
        interrupt(); //將中斷標志設置為true
        System.err.println("before thread1 interrupt status:"+isInterrupted());
        while (true){
            try {
                sleep(6000);
            } catch (InterruptedException e) {
                System.err.println("exception thread1 interrupt status:"+isInterrupted());
            }
        }
    }
}

靜態內部線程2:

private static class InterruptThread2 extends Thread{
    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while (true){
            if (System.currentTimeMillis() - start > 2000){
                interrupt();   //將中斷標志設置為true
                System.out.println("before  thread2 interrupt status:"+isInterrupted());
                break;
            }
       }
        Thread.interrupted();  //清除中斷標志,置為false
        System.out.println("after  thread2 interrupt status:"+isInterrupted());
    }
}

1、在InterruptThread 中先把中斷標志設置為true,并打印是否中斷,在主線程中再次將處于休眠狀態中的InterruptThread 再次中斷,此時會拋出異常,再次打印是否中斷。在這個類中我們要證明的是,拋出InterruptedException 線程中斷標志會被清除。

2、在InterruptThread2 中我們讓InterruptThread2 正常運行2s,并在循環中將中斷標志置為true,正常退出循環時,調用Thread.interrupted(); 清除中斷標志,并打印是否中斷。在這個類中我們要證明調用當前線程Thread.interrupted();會將中斷標志清除。

在這兩個類中證明了,java線程中斷是協作式的,將中斷標志置為true并不會立馬就中斷線程。

打印結果:

before thread1 interrupt status:true
exception thread1 interrupt status:false
before  thread2 interrupt status:true
after  thread2 interrupt status:false
exception thread1 interrupt status:false

四、創建線程

創建線程有三種方法:

  • 繼承Thread類
public class CreateThread {

    public static void main(String[] args) {
        ExtendThread extendThread = new ExtendThread();
        extendThread.start();
    }

    private static class ExtendThread extends Thread{
        @Override
        public void run() {
            System.out.println("class extends thread start");
        }
    }

}
  • 實現Runable接口
public class CreateThread {
    public static void main(String[] args) {

        Thread thread = new Thread(new ImplRunnable());
        thread.start();
    }

    private static class ImplRunnable implements Runnable{

        @Override
        public void run() {
            System.out.println("class implements Runnable start");
        }
    }
}
  • 實現Callable接口

Callable是可以獲取到返回值, 但是只能通過線程池來調用,它返回的是一個Future對象f,通過f.get()就能獲取到結果。

public class CreateThread {
    private static ExecutorService executorService = Executors.newFixedThreadPool(4);

    public static void main(String[] args) {
      
        Future<String> submit = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "success";
            }
        });

        try {
            String s = submit.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

能力一般,水平有限,如有錯誤,請多指出。
如果對你有用點個關注給個贊唄

更多文章可搜索關注微信公眾號 suncodernote

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 寫在前面的話: 這篇博客是我從這里“轉載”的,為什么轉載兩個字加“”呢?因為這絕不是簡單的復制粘貼,我花了五六個小...
    SmartSean閱讀 4,785評論 12 45
  • Java多線程基礎 1.多線程簡介 在了解多線程之前我們要先知道什么是進程和線程: 進程:進程是系統進行調度和分配...
    學習也是一種潮流閱讀 199評論 0 0
  • 上次學習到了如何停止線程。這次學習暫停線程,線程的優先級,什么是守護線程首先了暫停線程暫停的線程意味著此線程還可以...
    ccq_inori閱讀 121評論 0 0
  • 單任務 單任務的特點是排隊執行,也就是同步,就像再cmd輸入一條命令后,必須等待這條命令執行完才可以執行下一條命令...
    Steven1997閱讀 1,207評論 0 6
  • [TOC] 0 前言 為什么需要學習并發編程? 大廠JD硬性要求,也是高級工程師必經之路,幾乎所有的程序都需要并發...
    憩在河岸上的魚丶閱讀 481評論 0 3