1. 程式人生 > 資訊 >螞蟻集團 2023 春招正式開啟:技術人才佔比超八成,首次新增密碼演算法、Golang 語言等崗位

螞蟻集團 2023 春招正式開啟:技術人才佔比超八成,首次新增密碼演算法、Golang 語言等崗位

多執行緒學習

一、多執行緒概念

1.1、多執行緒定義

  • 程式:是為完成特定任務,用某種語言編寫的一組指令的集合,即指一段靜態的程式碼,靜態物件。

  • 程序:是正在執行的程式

    • 獨立性:程序是一個能獨立執行的基本單位,同時也是系統分配資源和排程的獨立單位
    • 動態性:程序的實質是程式的一次執行過程,程序是動態產生,動態消亡的
    • 併發性:任何程序都可以同其他程序一起併發執行
  • 執行緒:是程序中的單個順序控制流,是一條執行路徑

    • 單執行緒:一個程序如果只有一條執行路徑,則稱為單執行緒程式
    • 多執行緒:一個程序如果有多條執行路徑,則稱為多執行緒程式
  • 並行:在同一時刻,有多個指令在多個CPU上同時執行。

  • 併發:在同一時刻,有多個指令在單個CPU上交替執行。

1.2、多執行緒的排程方式

  • 分時排程模式:所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間。
  • 搶佔排程模式:優先讓優先順序較高的執行緒使用 CPU,如果優先順序相同,那麼會隨機選擇一個。
    • Java 使用的就是搶佔式排程模型,執行緒的執行是隨機的。
    • 執行緒預設優先順序是 5,優先順序範圍是 1-10。
    • 當執行的執行緒都是守護執行緒時,JVM 將退出。

二、實現多執行緒的多種方式

2.1、繼承 Thred 類

  • 方法介紹

    方法名 說明
    void run() 線上程開啟後,此方法將被呼叫執行
    void start() 使此執行緒開始執行,Java虛擬機器會呼叫run方法()
  • 實現步驟

    • 定義一個類MyThread繼承Thread類
    • 在MyThread類中重寫run()方法
    • 建立MyThread類的物件
    • 啟動執行緒
  • 程式碼演示

public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

//        my1.run();
//        my2.run();

        //void start() 導致此執行緒開始執行; Java虛擬機器呼叫此執行緒的run方法
        my1.start();
        my2.start();
    }
}
  • 注意:
    • 從寫 run()方法:應為 run()是用來封裝被執行緒執行的程式碼。
    • run():封裝被執行緒執行的程式碼,直接呼叫,相當於普通方法的呼叫。
    • start():啟動執行緒,然後由 JVM 調動此執行緒的 run()方法。

2.2、實現 Runnable 介面

  • Thread構造方法

    方法名 說明
    Thread(Runnable target) 分配一個新的Thread物件
    Thread(Runnable target, String name) 分配一個新的Thread物件和名稱
  • 實現步驟

    • 定義一個類MyRunnable實現Runnable介面
    • 在MyRunnable類中重寫run()方法
    • 建立MyRunnable類的物件
    • 建立Thread類的物件,把MyRunnable物件作為構造方法的引數
    • 啟動執行緒
  • 程式碼演示

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        //建立MyRunnable類的物件
        MyRunnable my = new MyRunnable();

        //建立Thread類的物件,把MyRunnable物件作為構造方法的引數
        //Thread(Runnable target)
//        Thread t1 = new Thread(my);
//        Thread t2 = new Thread(my);
        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,"坦克");
        Thread t2 = new Thread(my,"飛機");

        //啟動執行緒
        t1.start();
        t2.start();
    }
}

2.3、實現 Callable 介面

  • 方法介紹

    方法名 說明
    V call() 計算結果,如果無法計算結果,則丟擲一個異常
    FutureTask(Callable callable) 建立一個 FutureTask,一旦執行就執行給定的 Callable
    V get() 如有必要,等待計算完成,然後獲取其結果
  • 實現步驟

    • 定義一個類MyCallable實現Callable介面
    • 在MyCallable類中重寫call()方法
    • 建立MyCallable類的物件
    • 建立Future的實現類FutureTask物件,把MyCallable物件作為構造方法的引數
    • 建立Thread類的物件,把FutureTask物件作為構造方法的引數
    • 啟動執行緒
    • 再呼叫get方法,就可以獲取執行緒結束之後的結果。
  • 程式碼演示

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示執行緒執行完畢之後的結果
        return "答應";
    }
}
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //執行緒開啟之後需要執行裡面的call方法
        MyCallable mc = new MyCallable();

        //Thread t1 = new Thread(mc);

        //可以獲取執行緒執行完畢之後的結果.也可以作為引數傳遞給Thread物件
        FutureTask<String> ft = new FutureTask<>(mc);

        //建立執行緒物件
        Thread t1 = new Thread(ft);

        String s = ft.get();
        //開啟執行緒
        t1.start();

        //String s = ft.get();
        System.out.println(s);
    }
}

2.4、使用執行緒池

  • 執行緒池介紹
Executors 工具類 執行緒池的工廠類,用於建立並返回不同型別的執行緒池
Executors.newCachedThreadPool() 建立一個可根據需要建立新執行緒的執行緒池
Executors.newFixedThreadPool(n) 建立一個可重用固定執行緒數的執行緒池
Executors.newSingleThreadExecutor() 建立一個只有一個執行緒的執行緒池
Executors.newScheduledThreadPool(n 建立一個執行緒池,它可安排在給定延遲後執行命令或者定期地執行
  • 實現步驟
    • 建立實現 runnable 或 callable 介面方式的物件
    • 建立 executorservice 執行緒池
    • 將建立好的實現了 runnable 介面的物件 放入 executorservice 物件的 execute 方法中執行
    • 關閉執行緒池
  • 程式碼演示
public class NumberThread implements Runnable{


    @Override
    public void run() {
        for(int i = 0;i<=100;i++){
            if (i % 2 ==0 )
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<100; i++){
            if(i%2==1){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args){

        //建立固定執行緒個數為十個的執行緒池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //new一個Runnable介面的物件
        NumberThread number = new NumberThread();
        NumberThread1 number1 = new NumberThread1();

        //執行執行緒,最多十個
        executorService.execute(number1);
        executorService.execute(number);//適合適用於Runnable

        //executorService.submit();//適合使用於Callable
        //關閉執行緒池
        executorService.shutdown();
    }

}

2.5、不同實現方式對比

1、三種不同實現方式對比

  • 實現Runable、Callable介面
    • 好處:擴充套件性強,實現該介面的同時還可以繼承其他的類
    • 缺點: 程式設計相對複雜,不能直接使用Thread類中的方法
  • 繼承 Thread 類
    • 好處: 程式設計比較簡單,可以直接使用Thread類中的方法
    • 缺點: 可以擴充套件性較差,不能再繼承其他的類

2、runnable 和 callable 有什麼區別

  • Runnable 介面 run 方法無返回值;Callable 介面 call 方法有返回值,是個泛型,和Future、FutureTask配合可以用來獲取非同步執行的結果。
  • Runnable 介面 run 方法只能丟擲執行時異常,且無法捕獲處理;Callable 介面 call 方法允許丟擲異常,可以獲取異常資訊。
  • 注:Callalbe介面支援返回執行結果,需要呼叫FutureTask.get()得到,此方法會阻塞主程序的繼續往下執行,如果不呼叫不會阻塞。

三、Thred 中常見的方法

3.1、設定和獲取執行緒名稱

  • 方法介紹

    方法名 說明
    void setName(String name) 將此執行緒的名稱更改為等於引數name
    String getName() 返回此執行緒的名稱
    Thread currentThread() 返回對當前正在執行的執行緒物件的引用
  • 程式碼演示

    public class MyThread extends Thread {
        public MyThread() {}
        public MyThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName()+":"+i);
            }
        }
    }
    public class MyThreadDemo {
        public static void main(String[] args) {
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            //void setName(String name):將此執行緒的名稱更改為等於引數 name
            my1.setName("高鐵");
            my2.setName("飛機");
    
            //Thread(String name)
            MyThread my1 = new MyThread("高鐵");
            MyThread my2 = new MyThread("飛機");
    
            my1.start();
            my2.start();
    
            //static Thread currentThread() 返回對當前正在執行的執行緒物件的引用
            System.out.println(Thread.currentThread().getName());
        }
    }
    

3.2、執行緒休眠

  • 相關方法

    方法名 說明
    static void sleep(long millis) 使當前正在執行的執行緒停留(暫停執行)指定的毫秒數
  • 程式碼演示

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println(Thread.currentThread().getName() + "---" + i);
            }
        }
    }
    public class Demo {
        public static void main(String[] args) throws InterruptedException {
            /*System.out.println("睡覺前");
            Thread.sleep(3000);
            System.out.println("睡醒了");*/
    
            MyRunnable mr = new MyRunnable();
    
            Thread t1 = new Thread(mr);
            Thread t2 = new Thread(mr);
    
            t1.start();
            t2.start();
        }
    }
    

3.3、執行緒優先順序

  • 執行緒排程

    • 兩種排程方式

      • 分時排程模型:所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間片
      • 搶佔式排程模型:優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個,優先順序高的執行緒獲取的 CPU 時間片相對多一些
    • Java使用的是搶佔式排程模型

    • 隨機性

      假如計算機只有一個 CPU,那麼 CPU 在某一個時刻只能執行一條指令,執行緒只有得到CPU時間片,也就是使用權,才可以執行指令。所以說多執行緒程式的執行是有隨機性,因為誰搶到CPU的使用權是不一定的

  • 優先順序相關方法

    方法名 說明
    final int getPriority() 返回此執行緒的優先順序
    final void setPriority(int newPriority) 更改此執行緒的優先順序執行緒預設優先順序是5;執行緒優先順序的範圍是:1-10
  • 程式碼演示

    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "---" + i);
            }
            return "執行緒執行完畢了";
        }
    }
    public class Demo {
        public static void main(String[] args) {
            //優先順序: 1 - 10 預設值:5
            MyCallable mc = new MyCallable();
    
            FutureTask<String> ft = new FutureTask<>(mc);
    
            Thread t1 = new Thread(ft);
            t1.setName("飛機");
            t1.setPriority(10);
            //System.out.println(t1.getPriority());//5
            t1.start();
    
            MyCallable mc2 = new MyCallable();
    
            FutureTask<String> ft2 = new FutureTask<>(mc2);
    
            Thread t2 = new Thread(ft2);
            t2.setName("坦克");
            t2.setPriority(1);
            //System.out.println(t2.getPriority());//5
            t2.start();
        }
    }
    

3.4、守護執行緒

  • 相關方法

    方法名 說明
    void setDaemon(boolean on) 將此執行緒標記為守護執行緒,當執行的執行緒都是守護執行緒時,Java虛擬機器將退出
  • 程式碼演示

    public class MyThread1 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName() + "---" + i);
            }
        }
    }
    public class MyThread2 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + "---" + i);
            }
        }
    }
    public class Demo {
        public static void main(String[] args) {
            MyThread1 t1 = new MyThread1();
            MyThread2 t2 = new MyThread2();
    
            t1.setName("女神");
            t2.setName("備胎");
    
            //把第二個執行緒設定為守護執行緒
            //當普通執行緒執行完之後,那麼守護執行緒也沒有繼續執行下去的必要了.
            t2.setDaemon(true);
    
            t1.start();
            t2.start();
        }
    }
    

四、執行緒同步 //TODO

4.1、執行緒安全問題

1、執行緒安全問題定義

  • 當多個執行緒對同一個物件的例項變數,做寫(修改)的操作時,可能會受到其他執行緒的干擾,發生執行緒安全的問題

2、安全問題出現的條件

  • 是多執行緒環境
  • 有共享資料
  • 有多條語句操作共享資料

3、安全問題的特性

  • 原子性(Atomic):
    • 不可分割,訪問(讀,寫)某個共享變數的時候,從其他執行緒來看,該操作要麼已經執行完畢,要麼尚未發生。其他執行緒看不到當前操作的中間結果。 訪問同一組共享變數的原子操作是不能夠交錯的,如現實生活中從ATM取款。
java中有兩種方式實現原子性:
    1.鎖 :鎖具有排他性,可以保證共享變數某一時刻只能被一個執行緒訪問。
    2.CAS指令 :直接在硬體層次上實現,看做是一個硬體鎖。
  • 可見性(visbility):

    • 在多執行緒環境中,一個執行緒對某個共享變數更新之後,後續其他的執行緒可能無法立即讀到這個更新的結果。
    • 如果一個執行緒對共享變數更新之後,後續訪問該變數的其他執行緒可以讀到更新的結果,稱這個執行緒對共享變數的更新對其他執行緒可見。否則稱這個執行緒對共享變數的更新對其他執行緒不可見。
    • 多執行緒程式因為可見性問題可能會導致其他執行緒讀取到舊資料(髒資料)。
  • 有序性(Ordering):

    • 是指在什麼情況下一個處理器上執行的一個執行緒所執行的 記憶體訪問操作在另外一個處理器執行的其他執行緒來看是亂序的(Out of Order)
    • 亂序: 是指記憶體訪問操作的順序看起來發生了變化。