1. 程式人生 > 實用技巧 >多執行緒與併發(一)——概述、執行緒狀態

多執行緒與併發(一)——概述、執行緒狀態

iwehdio的部落格園:https://www.cnblogs.com/iwehdio/

1、多執行緒概述

  • 多執行緒:多條執行路徑,主執行緒和子執行緒並行交替執行。

  • 程序:是執行程式的一次執行過程,是系統資源分配的單位。在作業系統中執行的程式就是程序。

  • 執行緒:是獨立的執行路徑,是CPU排程和執行的單位。一個程序可以有多個執行緒。

  • 在一個程序中,如果開闢了多個執行緒,執行緒的執行由排程器安排排程。

  • 對於同一份資源操作,會存在資源搶奪的問題,需要加入併發控制。

  • 執行緒建立:

    • 三種方法:繼承Thread類、實現Runnable介面或實現Callable介面。

    • 繼承Thread類:

      • Thread類實現了Runnable介面。

      • 自定義執行緒類繼承Thread類,重寫run()方法,編寫執行緒 執行體。建立執行緒物件,呼叫start()方法啟動執行緒。

        public class TestThread01 extends Thread {
        
            @Override
            public void run() {
                //執行緒體
                for (int i = 0; i < 200; i++) {
                    System.out.println("子執行緒--" + i);
                }
            }
        
            public static void main(String[] args) {
                TestThread01 thread01 = new TestThread01();
                thread01.start();
                for (int i = 0; i < 1000; i++) {
                    System.out.println("主執行緒--" + i);
                }
            }
        }
        
      • 執行緒開啟不一定立即執行,由CPU排程執行。主執行緒和子執行緒是交替執行的。

    • 實現Runnable介面:

      • 實現run()方法,編寫執行緒執行體。建立執行緒物件(傳入實現類),呼叫start()方法啟動執行緒。

        public class TestThread02 implements Runnable {
        
            @Override
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println("子執行緒--" + i);
                }
            }
        
            public static void main(String[] args) {
                TestThread02 thread02 = new TestThread02();
                //傳入實現類
                new Thread(thread02).start();
                for (int i = 0; i < 1000; i++) {
                    System.out.println("主執行緒--" + i);
                }
            }
        }
        
      • Java是單繼承多實現,推薦使用實現Runnable介面的方法,方便一個物件被多個執行緒呼叫。

    • 實現Callable介面:

  • 初識併發問題:

    • 多個執行緒操作同一個資源的情況下,執行緒不安全,資料紊亂。

      public class TestThread03 implements Runnable {
      
          private int ticketNum = 10;
          @Override
          public void run() {
              while (true) {
                  if(ticketNum<=0) {
                      break;
                  }
                  //延時
                  try {
                      Thread.sleep(200);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
       			//獲取當前執行的執行緒名稱          
                  System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"張票");
              }
          }
      
          public static void main(String[] args) {
              TestThread03 testThread03 = new TestThread03();
      		//同一個物件傳入多個執行緒,設定執行緒名字
              new Thread(testThread03,"執行緒A").start();
              new Thread(testThread03,"執行緒B").start();
              new Thread(testThread03,"執行緒C").start();
          }
      }
      

2、先備知識

  • 靜態代理:

    • 代理物件和真實物件都要實現同一個介面。代理物件要代理真實物件。

    • 代理物件做真實物件做不了的事情,真實物件專注做自己的事情。

    • Thread物件和傳入的執行緒物件都實現了Runnable介面,Thread物件時傳入的執行緒物件的靜態代理。

      public class TestThread04 {
          public static void main(String[] args) {
              //類比 new Thread(testThread).start();
              new WeddingCompany(new You()).HappyMarry();
          }
      }
      
      
      interface Marry {
          public void HappyMarry();
      }
      
      class You implements Marry{
      
          @Override
          public void HappyMarry() {
              System.out.println("Marry");
          }
      }
      
      class WeddingCompany implements Marry{
          private Marry target;
      
          public WeddingCompany(Marry target) {
              this.target=target;
          }
      
          @Override
          public void HappyMarry() {
              before();
              this.target.HappyMarry();
              after();
          }
      
          private void after() {
              System.out.println("after");
          }
      
          private void before() {
              System.out.println("before");
          }
      }
      
  • Lamda表示式:

    • 避免匿名內部類定義過多,屬於函數語言程式設計的概念。

    • 函式式介面:只包含一個抽象方法的介面。

    • Lamda表示式的簡化過程:

      • 外部實現類 -> 靜態內部類 -> 區域性內部類 -> 匿名內部類 -> Lamda表示式。
      public class LamdaFunction {
          //3、靜態內部類
          static class ILike2 implements Like {
              @Override
              public void lamda(int p) {
                  System.out.println("靜態內部類"+ p);
              }
          }
          public static void main(String[] args) {
              Like like1 = new ILike1();
              like1.lamda(1);
              Like like2 = new ILike2();
              like2.lamda(2);
              //4、區域性內部類
              class ILike3 implements Like {
                  @Override
                  public void lamda(int p) {
                      System.out.println("區域性內部類"+ p);
                  }
              }
              Like like3 = new ILike3();
              like2.lamda(3);
              //5、匿名內部類
              Like like4 = new Like() {
                  @Override
                  public void lamda(int p) {
                      System.out.println("匿名內部類"+ p);
                  }
              };
              like4.lamda(4);
              //6、Lamda表示式
              Like like5 = (int p) -> {
                  System.out.println("Lamda表示式"+ p);
              };
              like5.lamda(5);
              //7、簡化的Lamda表示式
              Like like6 = p -> System.out.println("簡化的Lamda表示式"+ p);
              like6.lamda(6);
          }
      }
      //1、函式式介面
      interface Like {
          public void lamda(int p);
      }
      //2、外部實現類
      class ILike1 implements Like {
          @Override
          public void lamda(int p) {
              System.out.println("外部實現類"+ p);
          }
      }
      

3、執行緒狀態

  • 執行緒狀態:

  • 執行緒停止:

    • 不推薦使用JDK提供的已經過時的執行緒停止方法。
    • 建議執行緒正常停止。可以使用標誌位,對外提供方法改變表示,使執行緒停止下來。
    public class TestStop implements Runnable {
        private boolean flag = true;
        @Override
        public void run() {
            int i = 0;
            while (flag){
                System.out.println("thread run..." + i++);
            }
        }
    
        public void stop(){
            this.flag=false;
        }
    
        public static void main(String[] args) {
            TestStop testStop = new TestStop();
            new Thread(testStop).start();
            for (int i = 0; i < 100; i++) {
                System.out.println("main執行緒" + i);
                if(i==90){
                    testStop.stop();
                    System.out.println("執行緒停止");
                }
            }
        }
    }
    
  • 執行緒休眠:

    • Thread.sleep()指定當前執行緒阻塞的毫秒數。存在異常InterruptedException。
    • 時間達到後執行緒進入就緒狀態。
    • 每一個物件都有一個鎖,sleep不會釋放鎖。
  • 執行緒禮讓:

    • Thread.yield()禮讓執行緒,讓正在執行的執行緒暫停,但不阻塞。
    • 將執行緒從執行狀態轉為就緒狀態。
    • CPU重新排程,禮讓不一定成功。
  • 執行緒強制執行:

    • thread.join()合併執行緒,待此執行緒執行完成之後,再執行其他執行緒,其他執行緒阻塞。可以理解為插隊。

      public class TestJoin implements Runnable {
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  System.out.println("子執行緒join" + i);
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              TestJoin testJoin = new TestJoin();
              Thread thread = new Thread(testJoin);
              thread.start();
              for (int i = 0; i < 200; i++) {
                  if(i==100) {
                      thread.join();
                  }
                  System.out.println("main執行緒" + i);
              }
          }
      }
      
    • 執行緒在main執行緒執行到100之前都是CPU排程的,執行到100之後先執行子執行緒,main執行緒阻塞。

  • 觀測執行緒狀態:

    • Thread.State常量定義執行緒狀態:

      • NEW:新生,尚未啟動的執行緒。
      • RUNNABLE:Java虛擬機器中執行的執行緒。
      • BLOCKED:被阻塞等待監視器鎖定的執行緒。
      • WAITING:正在等待另一個執行緒執行特定動作的執行緒。
      • TIMED_WAITING:正在等待另一個執行緒執行動作達到指定等待實現的執行緒。
      • TERMINATED:已經退出的執行緒。
      public class TestState implements Runnable {
          @Override
          public void run() {
              for (int i = 0; i < 5; i++) {
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
      
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              TestState testState = new TestState();
              Thread thread = new Thread(testState);
      
              Thread.State state = thread.getState();
              System.out.println(state);
      
              thread.start();
              state = thread.getState();
              System.out.println(state);
      
              while (thread.getState()!=Thread.State.TERMINATED) {
                  Thread.sleep(100);
                  state = thread.getState();
                  System.out.println(state);
              }
              state = thread.getState();
              System.out.println(state);
          }
      }
      
  • 執行緒優先順序:

    • Java提供一個執行緒排程器來監控程式中啟動後進入就緒狀態的所有執行緒。執行緒排程器安裝優先順序判斷應該排程那個執行緒來執行。
    • 執行緒優先順序高只是意味著獲得排程的概率低,更可能優先執行,不是一定優先執行。
    • 執行緒的優先順序用數字表示,範圍從1到10。常量包括:
      • Thread.MIN_PRIORITY=1
      • Thread.MAX_PRIORITY=10
      • Thread.NORM_PRIORITY=5.
    • 使用方法getPriority()來獲取優先順序。
    • 使用方法setPriority(int xxx)來設定優先順序。
  • 守護執行緒:

    • 執行緒分為使用者執行緒和守護(daemon)執行緒。
    • 虛擬機器必須確保使用者執行緒執行完畢,不用等待守護執行緒執行完畢。
    • 設定執行緒為守護執行緒thread.setDaemon(true)

iwehdio的部落格園:https://www.cnblogs.com/iwehdio/