多線程基礎知識回顧

多線程的基礎知識複習

1)進程和線程的區別java

進程:
    程序運行後,一個QQ,微信等就是一個進程。
線程:
    線程是進程中的最小單元。說簡單的話說,線程就是程序中不一樣的執行路徑。
程序:
    QQ是一個程序,是一個硬盤上的程序,

2)線程run方法和start方法的區別segmentfault

public class T01_WhatIsThread {
    //新建了靜態內部類繼承Thrread,線程修改1秒,輸入T1
    private static class T1 extends Thread {
        @Override
        public void run() {
           for(int i=0; i<10; i++) {
               try {
                   TimeUnit.MICROSECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("T1");
           }
        }
    }
    //main方法
    public static void main(String[] args) {
        new T1().run();
        //new T1().start();
        for(int i=0; i<10; i++) {
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main");
        }

    }
}
輸出
T1
T1
main
main
//這個時候註釋18run方法 開始start方法 執行結果大不同
T1
main
T1
main

#結論 therad的start方法 執行路徑是分支的形式,而run方法是重上到下依次執行。

3)多線程的經常使用實現方法安全

//線程主要實現的方法有3種
public class T02_HowToCreateThread {
    //集成Thread的方法
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }
    //實現Runnable接口
    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }
    //三種線程不一樣的運行方式
    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRun()).start();
        //lamda表達式來執行一個線程
        new Thread(()->{
            System.out.println("Hello Lambda!");
        }).start();
    }
}
// lamda表達式也是一種方式,線程池也是一種方式
//Executors.newCachedThreadPool();
//經過線程池去拿到一個線程,而這個線程仍是要執行runable或者start的方法。

4)線程最基本的方法微信

  • 4.1 sleep多線程

    • 當前線程睡眠多少時間,由其餘線程來執行
  • 4.2 joinide

    • 經常使用於等待另一個線程結束,也就是保證線程有序執行。
      25456de9-85b5-4703-887b-22e1e45b6ecb.png
    //測試join線程
    static void testJoin() {
        Thread t1 = new Thread(()->{
            //線程1建立了10個線程
            for(int i=0; i<10; i++) {
                System.out.println("A" + i);
                try {
                    //每一個線程休眠500毫秒
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //線程2
        Thread t2 = new Thread(()->{
            try {
                //線程1的線程加入。等待線程1結束
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //線程1結束後,纔開始執行此代碼
            for(int i=0; i<10; i++) {
                System.out.println("B" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //分別啓動
        t1.start();
        t2.start();
    }
    #執行結果
    A0
    A1
    A2
    A3
    A4
    A5
    A6
    A7
    A8
    A9
    B0
    B1
    B2
    B3
    B4
    B5
    B6
    B7
    B8
    B9
  • 4.3 yield學習

    • 當前線程先離開,返回到就緒的狀態,至於這個線程是否會被CPU立刻執行,仍是先執行等待隊列中已經等待的線程,這個不必定。看競爭
線程的狀態圖

file

  • Ready(就緒) Running(正在運行) 都屬於Runnable狀態。
  • TimedWaiting 的相關方法是指時間一到,就會自動恢復runnable狀態
  • Waiting 狀態是指必須被喚醒才能進入Runnabel狀態
  • synchronized 獲得同步代碼塊的鎖 以前會進入阻塞狀態,獲得鎖以後,線程運行
  • Terminated 線程中止
  • 不建議用Thread的stop方法來強行中止線程,有安全問題。
  • 對於interrupt的一些說明:測試

    #線程經過wait()進入阻塞狀態,此時經過interrupt()中斷該線程;
    #調用interrupt()會當即將線程的中斷標記設爲「true」,可是因爲線程處於阻塞狀態,因此該「中斷標記」會當即被清除爲「false」,同時,會產生一個InterruptedException的異常。
    #咱們會catch這個異常,再根據業務邏輯去處理線程的後續行爲。
    #代碼示例
    @Override
    public void run() {
        try {
            // 1. isInterrupted()保證,只要中斷標記爲true就終止線程。
            while (!isInterrupted()) {
                // 執行任務...
            }
        } catch (InterruptedException ie) {  
            // 2. InterruptedException異常保證,當InterruptedException異常產生時,線程被終止。
        }
    }
    //interrupt 用於控制業務場景的用法極少,正經常使用法通常是某一個線程阻塞時間很長很長,經過interrupt來打斷線程。
  • 線程狀態的小例子this

    static class MyThread extends Thread {
            @Override
            public void run() {
                System.out.println(this.getState());
    
                for(int i=0; i<4; i++) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(i);
                }
            }
        }
    
        public static void main(String[] args) {
            Thread t = new MyThread();
    
            System.out.println(t.getState());
    
            t.start();
    
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(t.getState());
    
        }
        #輸出結果
        NEW
        RUNNABLE
        0
        1
        2
        3
        TERMINATED
synchronize 鎖
  • 須要注意的是 鎖,所的是對象,而不是代碼spa

    • 好比:

public class T {
    private int count = 10;
    private Object o = new Object();
    public void m() {
    synchronized(o) { // **任何線程要執行下面的代碼,必須先拿到o的鎖**
        count--;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
}
#問題 
上述代碼若是new了2個不一樣的object,o和o1,那synchronize(o) 是鎖誰呢,固然鎖o。

    ```
  • synchronize 的幾種鎖的形式

    • 鎖this

      private int count = 10;
      public void m() {
          synchronized(this) { //任何線程要執行下面的代碼,必須先拿到this的鎖
              count--;
              System.out.println(Thread.currentThread().getName() + " count = " + count);
          }
      }
    • 鎖方法

      private int count = 10;
      public synchronized void m() { //等同於在方法的代碼執行時要synchronized(this)
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
    • 鎖靜態方法

      private static int count = 10;
      public synchronized static void m() { //這裏等同於synchronized(T.class)
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
      
      public static void mm() {
          synchronized(T.class) { //考慮一下這裏寫synchronized(this)是否能夠?(不可,由於沒有new,)
              count --;
          }
      }
    • 小思考

      1) 下面的線程如何輸出?
      2)加上synchronize後又有什麼區別
      3)加上volatile後又什麼區別
      private /*volatile*/ int count = 100;
      
      public /*synchronized*/ void run() { 
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
      
      public static void main(String[] args) {
          T t = new T();
          for(int i=0; i<100; i++) {
              new Thread(t, "THREAD" + i).start();
          }
      }
      #1 打印列會出現重複或者實際減的數和打印的數不一致。
      //打印結果抽取異常部分
      THREAD81 count = 37
      THREAD70 count = 37
      #2 synchronize 既保證可見又保證一致性
      #3 volatile 保證可見性,這個變量改後立馬被線程發現。
      #4 加了synchronize就沒必要加volatile。
    • 同一個線程能夠同時掉加鎖和不加鎖的方法,並不會致使一個方法有鎖,而致使不加鎖的方法沒法執行。
    • 讀方法和寫方法是否都須要上鎖來保持一致,看具體的業務邏輯。若是寫上鎖,讀不上鎖,有可能髒讀數據,這個根據業務來定。讀上鎖會讓讀取的效率大大降。
    • synchronize 是可重入鎖,若是上鎖的方法1,調用上鎖的方法2,若是不能夠重入,會產生死鎖。
    • $\color{red}{程序若是出現異常,默認狀況下鎖會被釋放}$

      public class T {
      int count = 0;
      synchronized void m() {
          System.out.println(Thread.currentThread().getName() + " start");
          while(true) {
              count ++;
              System.out.println(Thread.currentThread().getName() + " count = " + count);
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              
              if(count == 5) {
                  int i = 1/0; //此處拋出異常,鎖將被釋放,要想不被釋放,能夠在這裏進行catch,而後讓循環繼續
                  System.out.println(i);
              }
          }
      }
      }}
      public static void main(String[] args) {
          T t = new T();
          Runnable r = new Runnable() {
      
              @Override
              public void run() {
                  t.m();
              }
              
          };
          new Thread(r, "t1").start();
          
          try {
              TimeUnit.SECONDS.sleep(3);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          
          new Thread(r, "t2").start();
      }
      }
      #//打印結果 人爲異常不補貨,t2獲得鎖。繼續執行
      t1 count = 2
      t1 count = 3
      t1 count = 4
      t1 count = 5
      t2 start
      Exception in thread "t1" t2 count = 6
      java.lang.ArithmeticException: / by zero
      at com.mashibing.juc.c_011.T.m(T.java:27)
      at com.mashibing.juc.c_011.T$1.run(T.java:39)
      at java.base/java.lang.Thread.run(Thread.java:844)
      t2 count = 7
    • synchronize的底層升級概

      推薦文章 鎖升級的小段子
      偏向鎖(誰來誰第一,誰偏向)----》
      競爭 自旋鎖,(自旋10次或者 JDK目前規定爲自旋線程超過CPU內核數的一半)
      若是超過了自旋的上限,就升級重量級鎖,重鎖是OS(操做系統)級別的鎖,並進入等待隊列。

    • 鎖使用的場景:

      $\color{red}{線程少,上鎖代碼執行快,用自旋鎖}$
      $\color{red}{線程多,上鎖代碼執行長,用OS重量鎖}$
  • 總結
    線程的概念、線程的啓用方式,經常使用的方法介紹。
    線程的狀態機、
    synchronize 鎖的是對象,不是代碼。
    synchronize 鎖的集中方式
    synchronize 的鎖的升級。
    異常鎖,默認放棄鎖。除非catch跳過循環。
    偏向鎖、自旋鎖 (公平、非公平)重量級鎖。
  • 做者QQ 68335397 有問題指正或者討論學習,留言也可。
相關文章
相關標籤/搜索