1. 程式人生 > 實用技巧 >東哥手把手帶你刷二叉樹(第三期)

東哥手把手帶你刷二叉樹(第三期)

多執行緒

1. 執行緒、程序簡介

程式是指令和資料的有序集合,其本身沒有任何執行的含義,是一個靜態的概念。

程序是執行程式的一次執行過程,是一個動態的概念。是系統資源分配的單位。 程序時系統分配的。

通常一個程序中包含多個執行緒,一個程序至少有一個執行緒。執行緒是CPU排程和執行的單位。


核心概念:

  1. 執行緒就是獨立的執行路徑
  2. 在程式執行時,即使沒有自己建立執行緒,後臺也會有多個執行緒,如主執行緒,gc執行緒(垃圾回收執行緒);
  3. main()被稱為主執行緒,是系統的入口,用於執行整個程式
  4. 在一個程序中,如果開闢了多個執行緒,執行緒的執行由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為干預的。
  5. 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制
  6. 執行緒會帶來額外的開銷,如cpu排程時間,併發控制開銷
  7. 每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料的不一致

2.執行緒建立

三種建立方式:

  1. 繼承Thread類
  2. 實現Runnable介面
  3. 實現Callable介面

一、Thread

  • 自定義執行緒類繼承Thread類
  • 重寫run()方法,編寫執行緒實體
  • 建立執行緒物件,呼叫start()方法啟動執行緒
public class TestThread1 extends Thread{
    @Override
    public void run() {
        //run方法執行緒體
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看程式碼--"+i);
        }
    }

    public static void main(String[] args) {
        TestThread1 testThread1 = new TestThread1();
       // 呼叫run()方法是先執行run()方法,在執行main()方法
//        testThread1.run();
//呼叫start()方法,是main()方法與start()方法交替執行輸出的時候也是交替輸出。
//所以不能呼叫run()方法進行執行執行緒
        testThread1.start();

        for (int i = 0; i < 900; i++) {
            System.out.println("我在學習多執行緒-"+i);
        }
    }
}

使用多執行緒以及IO進行網路圖片的下載

package com.lzp.thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class TestThread2 extends Thread{
    private String url;
    private String name;
    public TestThread2(String url, String name){
        this.name = name;
        this.url = url;
    }

    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.download(this.url,this.name);
        System.out.println("下載了" + this.name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://www.baidu.com/link?url=R82Nt0nRYu128ZqFYHOIt7v-xdFg7wNJodcc-LQSgePd2m2Za5_JS8PR8HB0eBtDlE2K1o-KWY8M_D6E6_x1yKOjUucaFqgXOdT01Se97ukTd9F3W4Ja0pIHGNvZL3PE1f0h3wlkvxo9pKWEgGv5RnaWEgerJBTw3MqOTiQquQpsN-_jvFjm-bcXiUd45QvJATU_UMGXmDGGuG0n7OQZ4U2IxN16Aj2vzlAWr569iuq1LTPCXYXDz0nDZdrKmHEga6pfuhq8yBAuMe1AC2j2AI8LT5a9u7ADAGYkspyn0MLPXUFqFixWmt2IjKpXMtAhM6urUaQ2BvppLLT0-OJhozH8Kov_YLNeLDB5EVhGoJICwSd9Da4af4fVZJ5tQiml9YwmXz6dnNd1TsPEJur09Dy23gbjFQqMHZFkHNJxB1Hmm-0ZzMx6_JIok-8hy6EkblRs9c-BGY_drTXzMw12RK84AFe0WrG0p_rU6b0ZpxdPF0Ch9FbN_lJ-PW_HuRHUHOCZE0Wc587NmWF7QiYiEul5QMLlLMLi8X2jgn2sp7BxyWWnyR9bLI3NXZK2ypgz-pcvmKn3yZ0Xm3BJkUGKjz1SWUXdsm8N4ChMplvc14Rd2K7crLPXPADo_VC8yMwiq1WUmW0MCQh2-6fE9K0eBjlrazZGm9dNbyQhqsj3-_W&timg=&click_t=1605149513263&s_info=1519_762&wd=&eqid=b5ee26b700000735000000055faca348","1.jpg");
        TestThread2 t2 = new TestThread2("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3083984867,3167144390&fm=26&gp=0.jpg","2.jpg");
        TestThread2 t3 = new TestThread2("https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=645805497,3565410143&fm=26&gp=0.jpg","3.jpg");

        t1.start();
        t2.start();
        t3.start();
    }
}

class WebDownloader{
    public void download(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("download出現問題");
        }
    }
}

根據輸出,圖片並不是按順序下載下來的,而是同時進行下載的,哪個先下載完那個先進行輸出。

二、Runnable

  • 定義MyRunnable類實現Runnable介面
  • 實現run()方法,編寫執行緒執行體
  • 建立執行緒物件,呼叫start()方法
public class TestThread3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("我在學習+" + i);
        }
    }

    public static void main(String[] args) {
        TestThread3 testThread3 = new TestThread3();
        //注意啟動執行緒的方法
        new Thread(testThread3).start();

        for (int i = 0; i < 900; i++) {
            System.out.println("我在玩遊戲+"+i);
        }
    }
}

同樣進行網路圖片下載

package com.lzp.thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class TestThread4 implements Runnable{
    private String url;
    private String name;
    public TestThread4(String url, String name){
        this.name = name;
        this.url = url;
    }

    @Override
    public void run() {
        WebDownloader1 webDownloader = new WebDownloader1();
        webDownloader.download(this.url,this.name);
        System.out.println("下載了" + this.name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2516762561,2728702104&fm=26&gp=0.jpg","4.jpg");
        TestThread2 t2 = new TestThread2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3003984691,41310498&fm=26&gp=0.jpg","5.jpg");
        TestThread2 t3 = new TestThread2("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3142750253,2237652291&fm=26&gp=0.jpg","6.jpg");

        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}

class WebDownloader1{
    public void download(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("download出現問題");
        }
    }
}

總結:

  • 繼承Thread類
    • 子類繼承Thread類具備多執行緒能力
    • 啟動執行緒:子類物件.start()
    • 不建議使用:避免OOP單繼承侷限性
  • 實現Runnable介面
    • 實現介面Runnable具有多執行緒能力
    • 啟動執行緒:傳入目標物件+Thread物件.start()
    • 推薦使用:避免單繼承侷限性,靈活方便,方便同一個物件被多個執行緒使用

多個執行緒操作同一個資源:

package com.lzp.thread;

//多個執行緒同時操作一個物件
//買火車票的例子

//問題是:多個執行緒操作同一個資源的情況下,執行緒不安全,資料紊亂
public class TestThread5 implements Runnable{

    private int ticketNums;

    public TestThread5(int ticketNums){
        this.ticketNums = ticketNums;
    }

    @Override
    public void run() {
        while(ticketNums>0){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "張票");
        }
    }

    public static void main(String[] args) {
        TestThread5 testThread5 = new TestThread5(10);

        new Thread(testThread5,"小明").start();
        new Thread(testThread5,"老師").start();
        new Thread(testThread5,"黃牛").start();

    }
}

多執行緒模擬龜兔賽跑

package com.lzp.thread;

//模擬龜兔賽跑
public class Race implements Runnable{

    private static String winner;


    @Override
    public void run() {
        for(int i=0;i<=100;i++){
            
              //模擬兔子休息
//            if(Thread.currentThread().getName().equals("兔子") && i%10==0){
//                try {
//                    Thread.sleep(5);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
            
            boolean flag = isGameOver(i);
            if(flag){
                break;
            }

            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
        }

    }

    public boolean isGameOver(int step){
        if (winner != null) {
            return true;
        }else {
            if(step >= 100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is"+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race,"兔子").start();
        new Thread(race,"烏龜").start();
    }
}

三、Callable

  • 實現Callable介面,需要返回值型別
  • 重寫call方法,需要丟擲異常
  • 建立目標物件
  • 建立執行服務: ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交執行:Future result1 = ser.submit(t1);
  • 獲取結果:boolean r1 = result.get();
  • 關閉服務:ser.shutdownNow();

可以定義返回值

可以丟擲異常

package com.lzp.thread;

import java.util.concurrent.*;

public class TestCallable implements Callable<Boolean> {
    private String url;
    private String name;
    public TestCallable(String url, String name){
        this.name = name;
        this.url = url;
    }

    @Override
    public Boolean call() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.download(this.url,this.name);
        System.out.println("下載了" + this.name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=226931949,1036395346&fm=26&gp=0.jpg","7.jpg");
        TestCallable t2 = new TestCallable("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=661299091,4083766848&fm=26&gp=0.jpg","8.jpg");
        TestCallable t3 = new TestCallable("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3246233636,10654504&fm=26&gp=0.jpg","9.jpg");

        //建立執行服務:
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交執行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);

        //獲取結果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        //關閉服務
        ser.shutdown();

    }
}

Lamda表示式

使用lamda表示式可以:

  1. 避免匿名內部類定義過多
  2. 可以讓程式碼看起來更簡潔
  3. 去掉了沒有意義的程式碼,只留下核心的邏輯

函式式介面定義:

  • 任何介面,如果只包含唯一一個抽象方法,那麼他就是一個函式式介面。
  • 對於函式式介面,可以通過lamda表示式來建立該介面的物件。
package com.lzp.lambda;

public class TestLambda1 {

    //3.靜態內部類
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("I like lambda2");
        }
    }

    public static void main(String[] args) {
        //實現外部類的程式碼
        ILike like = new Like();
        like.lambda();

        //實現靜態內部類的程式碼
        like = new Like2();
        like.lambda();

        //4.區域性內部類
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("I like lambda3");
            }
        }
        like = new Like3();
        like.lambda();

        //5.匿名內部類,沒有類的名稱,必須藉助介面或者父類
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };
        like.lambda();

        //6.lambda表示式簡化類
        like = ()->{
            System.out.println("I like lambda5");
        };

        like.lambda();


    }
}

//1.定義一個函式式介面
interface ILike{
    void lambda();
}

//2.實現類
class Like implements ILike {
    @Override
    public void lambda() {
        System.out.println("I like lambda1");
    }

}

package com.lzp.lambda;

public class TestLambda2 {
    public static void main(String[] args) {

        ILove love = null;

        love = (int a)->{
                System.out.println("I Love you " + a);
        };

        //簡化,可以去掉型別,
        //可以去掉小括號,前提是隻有一個引數
        //還可以去掉大括號,前提是表示式裡只有一行程式碼
        love = (a)->{
            System.out.println("I Love you " + a);
        };


        love.love(520);
    }
}

interface ILove{
    void love(int a);
}

3.執行緒狀態

五大狀態:

  1. 建立狀態
  2. 就緒狀態
  3. 阻塞狀態
  4. 執行狀態
  5. 死亡狀態

執行緒方法:

1.停止執行緒:

package com.lzp.state;

//測試stop
//1.建議征程停止,利用次數,不建議死迴圈
//2.建議使用標誌位,設定一個標誌位
//3.不要使用stop或者destroy等過時或者JDK不建議使用的方法

public class TestStop implements Runnable{

    private boolean flag = true;


    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("Thread "+ i++);
        }
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for(int i=0;i<500;i++){
            System.out.println("main "+i);
            if(i==400){
                testStop.flag  = false;
                System.out.println("執行緒停止了");
            }
        }
    }
}

2.執行緒休眠(sleep):

  • sleep指定當前執行緒阻塞的毫秒數
  • sleep存在異常InterruptedException
  • sleep時間達到後執行緒進入就緒狀態
  • sleep可以模擬網路延時、倒計時等
  • 每一個物件都有一個鎖,sleep不會釋放