Thread:線程類

本文總結自,B站-遇見狂神說

1. 進程與線程

在這裏插入圖片描述

程序

指令和數據的有序集合,本身沒有任何運行的含義,是一個靜態的概念。

進程(Process)

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

線程

  • 一個進程可以包含若干個線程
  • 一個進程中至少有一個線程(不然沒有意義)
  • 線程是CPU調度和執行的單位

多線程

真正的多線程是指有多個CPU,即多核。如果是模擬出來的多線程(即一個PCU的情況下),在同一時間點,CPU只能執行一個代碼,因爲切換的很快,所以產生同時執行的錯覺。

2. 核心內容

  • 線程就是獨立的執行路徑
  • 在程序運行時,即使沒有自己創建線程,後臺也會有多個線程,如主線程,gc線程。
  • 主線程main(),爲系統的入口,用於執行整個程序。
  • 在一個進程中,如果開闢了多個線程,線程 的運行由調度器安排,調度器是與操作系統緊密相關的,先後順序是不能人爲干預的。
  • 對同一份資源操作時,會存在搶奪資源的問題,需要加入併發控制;
  • 線程會帶來額外開銷,如CPU調度時間,併發控制開銷;
  • 每個線程在自己的工作內存交互,內存控制不當會造成數據不一致;

3. 三種創建方式

image-20200618091917713

繼承Thread類

public class ThreadExtends extends Thread {
    @Override
    public void run(){
        // run方法線程體
        for (int i = 0; i <= 20; i++) {
            System.out.println("run方法線程體--"+ i);
        }
    }

    public static void main(String[] args) {
        // main線程,主線程

        ThreadExtends t = new ThreadExtends();

        // 開啓線程(和主線程同時跑)
        t.start();
        // 先跑子線程
        // t.run();

        for (int i = 0; i <= 20; i++) {
            System.out.println("主線程--"+ i);
        }
    }
}

結果:

...
run方法線程體--6
run方法線程體--7
run方法線程體--8
主線程--2
run方法線程體--9
主線程--3
...

案例:網圖下載

實現多線程下載圖片【導包:commons-io-2.7.jar】

public class ThreadDown_Extends extends Thread {

    /**
     * 網絡圖片地址
     */
    private String url;
    /**
     * 保存的文件名
     */
    private String name;

    /**
     * 全參構造方法
     * @param url
     * @param name
     */
    public ThreadDown_Extends(String url, String name) {
        this.url = url;
        this.name = name;
    }

    /**
     * 下載圖片線程的執行體
     */
    @Override
    public void run(){
        WebDownloader web = new WebDownloader();
        web.downloader(url,name);
        System.out.println("下載了文件名爲:"+name);
    }
}

下載類

public class WebDownloader{
    /**
     * 下載方法
     * @param url
     * @param name
     */
    public void downloader(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO異常,downloder方法出現問題...");
        }
    }
}

測試類

public class TestThread {
    public static void main(String[] args) {
        String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
        long times = System.currentTimeMillis();
        String name = times+".png";
        testExtends(url, name);
    }
    /**
     * 測試創建多線成方法:繼承線程類
     */
    private static void testExtends(String url, String name) {
        ThreadDown_Extends td1 = new ThreadDown_Extends(url, name);
        ThreadDown_Extends td2 = new ThreadDown_Extends(url, name);
        ThreadDown_Extends td3 = new ThreadDown_Extends(url, name);

        td1.start();
        td2.start();
        td3.start();
    }

}

實現Runable接口

public class ThreadRunnable implements Runnable {
    @Override
    public void run(){
        // run方法線程體
        for (int i = 0; i <= 20; i++) {
            System.out.println("run方法線程體--"+ i);
        }
    }

    public static void main(String[] args) {
        // main線程,主線程

        ThreadRunnable r = new ThreadRunnable();

        // 調用開啓多線程(兩個線程同時跑)
        new Thread(r).start();

        for (int i = 0; i <= 20; i++) {
            System.out.println("主線程--"+ i);
        }
    }
}

案例:網圖下載

public class ThreadDown_Runnable implements Runnable {
    /**
     * 網絡圖片地址
     */
    private String url;
    /**
     * 保存的文件名
     */
    private String name;

    /**
     * 全參構造方法
     * @param url
     * @param name
     */
    public ThreadDown_Runnable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    /**
     * 下載圖片線程的執行體
     */
    @Override
    public void run(){
        WebDownloader web = new WebDownloader();
        web.downloader(url,name);
        System.out.println("下載了文件名爲:"+name);
    }
}

下載類【與繼承Thread類案例相同】

測試類

public class TestThread {
    public static void main(String[] args) {
        String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
        long times = System.currentTimeMillis();
        String name = times+".png";
      
        testRunnable(url, name);
    }

    /**
     * 測試創建多線成方法:實現Runnable接口
     */
    private static void testRunnable(String url, String name) {
        // 創建Runnable接口的實現類對象
        ThreadDown_Runnable r = new ThreadDown_Runnable(url, name);
        // 創建線程對象,通過線程對象來開啓我們的線程,【代理】
        /*Thread t = new Thread(r);
        t.start();*/

        // 簡潔版
        new Thread(r).start();
    }
}

案例:龜兔賽跑

  1. 首先來個賽道距離,然後要離終點越來越近
  2. 判斷比賽是否結束
  3. 打印出勝利者
  4. 龜兔賽跑開始
  5. 模擬兔子睡覺
  6. 最終烏龜贏得比賽

代碼:

package cn.luis.race;

/**
 * @ClassName Race
 * @Description TODO :龜兔賽跑,(兔子休息,烏龜贏得比賽)
 * @Author L
 * @Date 2020.06.15 0:13
 * @Version 1.0
 * @Remark
 **/
public class Race implements Runnable {

    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {

            // 模擬兔子睡覺(名爲兔子的進程和每走十步,兔子睡一下)
            if ("流氓兔".equals(Thread.currentThread().getName()) && i % 10 == 0) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 判斷比賽是否結束
            boolean flag = gameOver(i);
            // 如果比賽結束了
            if (flag) {
                break;
            }

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

    /**
     * 判斷是否完成比賽
     * @param step
     * @return
     */
    private boolean gameOver(int step) {

        // 判斷是否有獲勝者
        if (winner != null) {
            // 存在
            return true;
        }
        if (step >= 100) {
            winner = Thread.currentThread().getName();
            System.out.println("Winner is : " + winner);
            return true;
        }
        return false;
    }

    /**
     * 主方法
     * @param args
     */
    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race, "小烏龜").start();
        new Thread(race, "流氓兔").start();
    }
}

結果:

...
小烏龜 -- 跑了 --97 --步
小烏龜 -- 跑了 --98 --步
小烏龜 -- 跑了 --99 --步
Winner is : 小烏龜

實現Callable接口

  1. 實現此接口需要返回值類型

  2. 從寫call方法,需要拋出異常

  3. 創建目標對象

  4. 創建執行服務

    ExecutorService service = Executors.newFixedThreadPool(3);
    
  5. 提交執行

    Future<Boolean> r1 = service.submit(c1);
    
  6. 獲取結果

    boolean rs1 = r1.get();
    
  7. 關閉服務

    service.shutdownNow();
    

案例:網圖下載

package cn.luis.callable;

import cn.luis.down.WebDownloader;

import java.util.concurrent.Callable;

/**
 * @ClassName ThreadDown
 * @Description TODO 實現Callable接口:實現多線程下載圖片
 * @Author L
 * @Date 2020.06.14 10:39
 * @Version 1.0
 * @Remark
 **/
public class ThreadDown_Callable implements Callable<Boolean> {
    /**
     * 網絡圖片地址
     */
    private String url;
    /**
     * 保存的文件名
     */
    private String name;

    /**
     * 全參構造方法
     * @param url
     * @param name
     */
    public ThreadDown_Callable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    /**
     * 下載圖片線程的執行體
     */
    @Override
    public Boolean call(){
        WebDownloader web = new WebDownloader();
        web.downloader(url,name);
        System.out.println("下載了文件名爲:"+name);
        return true;
    }
}

測試類:

package cn.luis.callable;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @ClassName TestThread
 * @Description TODO 測試類
 * @Author L
 * @Date 2020.06.14 11:45
 * @Version 1.0
 * @Remark TODO callable接口好處:1.可以定義返回值 2.可以拋出異常
 **/
public class TestThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        String url = "https://img-blog.csdnimg.cn/20200409100541773.png";
        long times = System.currentTimeMillis();
        String name = times + ".png";
        testCallable(url, name);
    }

    /**
     * 測試創建多線程方法:實現Callable接口
     */
    private static void testCallable(String url, String name) throws ExecutionException, InterruptedException {
        // 創建Callable接口的實現類對象
        ThreadDown_Callable c1 = new ThreadDown_Callable(url, name);
        ThreadDown_Callable c2 = new ThreadDown_Callable(url, name);
        ThreadDown_Callable c3 = new ThreadDown_Callable(url, name);

        // 創建執行服務
        ExecutorService service = Executors.newFixedThreadPool(3);

        // 提交執行
        Future<Boolean> r1 = service.submit(c1);
        Future<Boolean> r2 = service.submit(c2);
        Future<Boolean> r3 = service.submit(c3);

        // 獲取結果(可以拋出異常)
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        // 打印結果
        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        // 關閉服務
        service.shutdownNow();
    }

}

4. Lambda表達式

在這裏插入圖片描述

  • λ希臘字母中排序第十一位的字母。

  • 避免匿名內部類定義過多,只展示核心邏輯。

  • 函數式(接口)編程思想:接口只包含唯一一個抽象方法

代碼:

接口

public interface ILike {
    void like();
}

實現類

public class Like implements ILike {
    @Override
    public void like() {
        System.out.println("i like lambda 1!");
    }
}

測試類

public class testLambda {

    // 第二種:靜態內部類
    static class Like2 implements ILike {
        @Override
        public void like() {
            System.out.println("i like lambda 2!");
        }
    }

    public static void main(String[] args) {
        // 第一種:接口引用指向實現類
        ILike like = new Like();
        like.like();

        // // 第二種:靜態內部類的使用
        like = new Like2();
        like.like();

        // 第三種:局部內部類
        class Like3 implements ILike {
            @Override
            public void like() {
                System.out.println("i like lambda 3!");
            }
        }
        like = new Like3();
        like.like();

        // 第四種:匿名內部類,沒有類的名字,必須藉助接口或者父類
        like = new ILike() {
            @Override
            public void like() {
                System.out.println("i like lambda 4!");
            }
        };
        like.like();

        // 第五種:lambda表達式
        like = () -> System.out.println("i like lambda 5!");
        like.like();
    }
}

表達式簡化

  1. 參數類型,要麼都寫要麼都不寫

     (a,b)-> {...}
    
  2. 省略大括號,只有一條語句時纔可以

    (a,b)-> System.out.println("aaa");
    
  3. 省略參數小括號,只有一個參數時纔可以

    c -> System.out.println("aaa");
    

5. 靜態代理

在這裏插入圖片描述

要求:

  1. 真實對象和代理對象都要實現同一接口
  2. 代理對象必須要代理真實角色(將真實角色傳入)

優點:

  1. 代理對象可以做很多真實對象做不了的事情
  2. 真實對象專注做自己的事情

案例:婚慶公司和結婚

接口

public interface Marry {
    void happyMarry();
}

新郎

public class Xinlang implements Marry {
    @Override
    public void happyMarry() {
        System.out.println("小明要結婚啦!");
    }
}

婚慶公司:幫助你結婚(是個代理角色)

public class HunQing implements Marry {

    /**
     * 代理誰 --> 真實目標角色
     */
    private Marry target;

    public HunQing(Marry target) {
        this.target = target;
    }

    /**
     * 相當於增強了方法(功能多了)
     */
    @Override
    public void happyMarry() {
        before();
        // 真實對象調用方法
        this.target.happyMarry();
        after();
    }

    /**
     * 婚後
     */
    private void after() {
        System.out.println("婚後--收尾款");
    }

    /**
     * 婚前
     */
    private void before() {
        System.out.println("婚前--佈置婚禮殿堂");
    }
}

測試類

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

        new HunQing(new Xinlang()).happyMarry();
        // 線程底部原理與結婚案例相同
        new Thread(() -> System.out.println("I love You!")).start();
    }
}

6. 線程狀態

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

7. 線程操作

在這裏插入圖片描述

線程方法

方法 說明
setPriority(int newPriority) 更改線程的優先級
static void sleep(long millis) 休眠
void join() 等待該線程終止
static void yield() 暫停單籤正在執行的線程對象,並執行其他線程
void interrupt() 中斷線程【廢棄】
boolean isAlive() 測試線程是否處於活動狀態

停止線程

在這裏插入圖片描述

  1. 利用次數,不建議死循環
  2. 建議使用標誌位
  3. 不要使用JDK不建議的方法,如:stop,destory

代碼:

public class TestStop implements Runnable {

    /**
     * 1.設置標誌位
     */
    private boolean flag = true;

    /**
     * 重寫線程執行方法
     */
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("子線程 -- " + i++);
        }
    }

    /**
     * 2.設置一個公開的方法停止線程,轉換標誌位
     */
    public void stop() {
        this.flag = false;
    }

    /**
     * 主方法
     *
     * @param args
     */
    public static void main(String[] args) {
        // 常見Runnable接口實現類
        TestStop stop = new TestStop();

        // 模擬多線程
        for (int i = 0; i < 10; i++) {
            System.out.println("主線程 -- " + i);
            // 滿足條件時停止子線程
            if (i == 9) {
                stop.stop();
                System.out.println("線程該停止了!");
            }

            // 開啓多線程
            new Thread(stop).start();
        }
    }
}

結果:

主線程 -- 0
主線程 -- 1
主線程 -- 2
主線程 -- 3
線程該停止了!
主線程 -- 4

線程休眠

在這裏插入圖片描述
格式

 Thread.sleep(1000);

代碼:

package cn.luis.state;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 線程休眠
 * 1. 模擬網絡延時:買票
 * 2. 倒計時
 **/
public class TestSleep {

    public static void main(String[] args) {
       /* down1();
        down2();*/
        time();
    }

    /**
     * 打印當前系統時間
     */
    private static void time() {
        
        while (true) {
            try {
                Thread.sleep(1000);
                Date date = new Date();
                System.out.println(new SimpleDateFormat("YYYY:HH:mm:ss").format(date));

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 倒計時2
     */
    private static void down2() {
        int num = 5;
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(num--);
            if (num <= 0) {
                break;
            }
        }
        System.out.println("時間到!");
    }

    /**
     * 倒計時1
     */
    public static void down1() {
        for (int i = 5; i > 0; i--) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
        System.out.println("時間到!");
    }
}

線程禮讓

在這裏插入圖片描述
格式

 Thread.yield();

代碼:

禮讓不一定成功!

public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"線程開始執行!");
        // 禮讓
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"線程停止執行!");
    }
}

結果:(禮讓成功)

a線程開始執行!
b線程開始執行!
a線程停止執行!
b線程停止執行!

線程強制執行

在這裏插入圖片描述

相當於插隊

格式:(中斷異常)

Thread.join();

代碼:

public class TestJoin implements Runnable {

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 3; i++) {
            System.out.println("線程VIP來啦!");
        }
    }

    public static void main(String[] args) {
        TestJoin join = new TestJoin();
        // 啓動子線程
        Thread t = new Thread(join);
        t.start();

        // 主線程
        for (int i = 0; i < 5; i++) {
            if (i == 2) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main" + i);
        }
    }
}

結果:

main0
main1
線程VIP來啦!
線程VIP來啦!
線程VIP來啦!
main2
main3
main4

觀測線程狀態

在這裏插入圖片描述

格式:

Thread.State state = thread.getState();

代碼:

public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("線程終止啦~");
        });

        // 觀察狀態
        Thread.State state = thread.getState();
        // NEW
        System.out.println(state);

        // 觀察啓動後
        // 啓動線程
        thread.start();
        // RUN
        System.out.println(thread.getState());

        // 只要線程不終止,就一直輸出狀態
        while(state != Thread.State.TERMINATED){
            Thread.sleep(100);
            // 更新線程狀態
            state = thread.getState();
            // 輸出狀態
            System.out.println(state);
        }
        // 線程中斷或者結束後不能再次啓動
        thread.start();
    }
}

結果:

NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
...
TIMED_WAITING
線程終止啦~
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.base/java.lang.Thread.start(Thread.java:804)
	at cn.luis.state.TestState.main(TestState.java:44)

線程優先級

在這裏插入圖片描述

並不是一定按照優先級執行!

格式:

thread.setPriority(1);

thread.setPriority(Thread.MAX_PRIORITY);

代碼:

public class TestPriority {
    public static void main(String[] args) {
        // 主線程默認優先級
        System.out.println(Thread.currentThread().getName() + " -- " + Thread.currentThread().getPriority());
        MyPriority priority = new MyPriority();

        Thread t1 = new Thread(priority);
        Thread t2 = new Thread(priority);
        Thread t3 = new Thread(priority);
        Thread t4 = new Thread(priority);
        Thread t5 = new Thread(priority);
        Thread t6 = new Thread(priority);

        // 先設置優先級,再啓動
        t1.start();
        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(6);
        t4.start();

        t5.setPriority(8);
        t5.start();

        // 10
        t6.setPriority(Thread.MAX_PRIORITY);
        t6.start();

    }
}

class MyPriority implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " -- " + Thread.currentThread().getPriority());
    }
}

結果:

main -- 5
Thread-5 -- 10
Thread-4 -- 8
Thread-0 -- 5
Thread-3 -- 6
Thread-2 -- 4
Thread-1 -- 1

8. 守護線程

在這裏插入圖片描述

格式:

thread.setDaemon(true);

代碼;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        Life life = new Life();
        Thread thread = new Thread(god);
        // 默認是false表示是用戶線程,正常的線程都是用戶線程
        thread.setDaemon(true);

        // 上帝守護線程啓動(上帝線程會一直運行,直到life線程掛了,虛擬機不用等待守護線程執行完畢)
        thread.start();
        // 生命線程啓動
        new Thread(life).start();

    }
}

/**
 * 上帝
 */
class God implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("守護線程...");
        }
    }
}

/**
 * 生命
 */
class Life implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("用戶線程正在活躍...!");
        }
        System.out.println("用戶線程掛啦!");
    }
}

結果:(虛擬機無需等待守護線程停止)

守護線程...
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程正在活躍...!
用戶線程掛啦!

9. 線程同步

在這裏插入圖片描述

同一進程的多個線程共享同一塊存儲空間,在方便的同時也帶來了訪問衝突問題,爲了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制

當一個線程獲得對象的排它鎖,獨佔資源,其他線程必須等待它用完後釋放鎖,有可能存在以下問題:

  • 一個線程持有鎖會導致其他所有需要此鎖的線程掛起;
  • 在多線程競爭下,加鎖和釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題;
  • 若一個優先級高的線程等待優先級低的線程釋放鎖,會導致優先級倒置沒因其性能問題。

同步方法

同步方法:synchonized默認鎖的對象是this,也就是類本身

案例:買票

class BuyTicket implements Runnable {

    private int ticketNum = 5;
    // 標誌位
    boolean flag = true; 

    @Override
    public void run() {
        // 買票
        while (flag) {
            buy();
        }
    }

    /**
     * 同步鎖方法,鎖的是this
     */
    private synchronized void buy() {
        // 判斷是否有票
        if (ticketNum <= 0) {
            flag = false;
            return;
        }
        // 模擬延時
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 買票
        System.out.println(Thread.currentThread().getName() + "拿到-- " + ticketNum-- + " --票");
    }
}

測試類:

public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"小明").start();
        new Thread(buyTicket,"小李").start();
        new Thread(buyTicket,"黃牛").start();
    }
}

結果:

小明拿到-- 5 --票
小明拿到-- 4 --票
黃牛拿到-- 3 --票
小李拿到-- 2 --票
黃牛拿到-- 1 --

同步代碼塊

Obj:同步監視器,也就是代碼塊裏要操作的對象,【可以鎖任何對象,鎖增刪改的對象就好】

案例:銀行取錢

賬戶類

class Account {
    /**
     * 餘額
     */
    int nowMoney;

    /**
     * 構造方法
     *
     * @param nowMoney
     */
    public Account(int nowMoney) {
        this.nowMoney = nowMoney;

    }
}

銀行:模擬取款

class Bank extends Thread {
    /**
     * 賬戶
     */
    Account account;

    /**
     * 取了多少錢
     */
    int quMoney;

    public Bank(Account account, int quMoney, String name) {
        super(name);
        this.account = account;
        this.quMoney = quMoney;

    }

    /**
     * 同步方法:synchonized默認鎖的對象是this,也就是類本身
     * 同步塊:  Obj:同步監視器,也就是代碼塊裏要操作的對象,【可以鎖任何對象,鎖增刪改的對象就好】
     */
    @Override
    public void run() {
        synchronized (account) {
            // 判斷有沒有錢
            if (account.nowMoney - quMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "錢不夠!");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 手裏的錢
            //System.out.println(this.getName()+Thread.currentThread().getName());
            System.out.println(this.getName() + "手裏的錢:" + this.quMoney);
            // 卡內餘額 = 餘額 - 取出的錢
            account.nowMoney = account.nowMoney - quMoney;
            System.out.println("卡內餘額爲:" + account.nowMoney);
        }

    }
}

測試類:

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100);

        Bank you = new Bank(account, 70, "二萌");
        Bank her = new Bank(account, 40, "阿雷");

        you.start();
        her.start();

    }
}

結果:

二萌手裏的錢:70
卡內餘額爲:30
阿雷錢不夠

線程不安全的集合:ArrayList

public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        // 模擬延時
        Thread.sleep(100);
        // 主線程
        System.out.println(Thread.currentThread().getName()+"線程,集合中存儲了多少個線程:"+list.size());
    }
}

結果:

main線程,集合中存儲了多少個線程:10

Lock鎖【JDK1.5】

在這裏插入圖片描述

通過顯式定義同步鎖對象來實現同步,同步鎖使用Lock對象充當

ReentrantLock類實現了Lock接口,可重入鎖,與synchonized有相同的併發性和內存語義。

格式:

private final ReentrantLock lock = new ReentrantLock();

可以顯式加鎖、釋放鎖:【必須在try—catch–finally代碼塊中】

try {
    // 加鎖
    lock.lock();
	...
} catch (Exception e) {
    e.printStackTrace();
} finally {
    // 解鎖
    lock.unlock();
}

代碼:

package cn.luis.lock;

import java.util.concurrent.locks.ReentrantLock;

class BuyTicket implements Runnable {
    int ticketNum = 5;

    /**
     * 定義Lock鎖
     * ReentrantLock:可重入鎖
     */
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 加鎖
                lock.lock();
                if (ticketNum > 0) {
                    // 模擬延時(睡眠)
                    Thread.sleep(200);
                    // 打印線程名稱
                    System.out.println(Thread.currentThread().getName() + " -- 拿到了第-- " + ticketNum-- + " --張票");
                } else {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 解鎖
                lock.unlock();
            }

        }
    }
}

測試類:

public class TestLock {
    public static void main(String[] args) {
        BuyTicket b = new BuyTicket();
        new Thread(b).start();
        new Thread(b).start();
        new Thread(b).start();
    }

}

結果:

Thread-0 -- 拿到了第-- 5 --張票
Thread-2 -- 拿到了第-- 4 --張票
Thread-1 -- 拿到了第-- 3 --張票
Thread-0 -- 拿到了第-- 2 --張票
Thread-0 -- 拿到了第-- 1 --張票

死鎖

產生原因

  • 多個線程各自佔有一下共享資源,並且互相等待其他線程線程佔有的資源才能運行,導致兩個或多個線程都在等待對方釋放資源,都停止執行的情況
  • 某一個同步塊同時擁有兩個以上對象的鎖,就可能發生此問題。

死鎖避免方法

產生死鎖的四個必要條件:

  1. 互斥條件:一個資源每次只能被一個進程使用
  2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:進程以獲得資源,再未使用完之前,不能強行剝奪
  4. 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。

只要破壞其中的任意一個或多個即可!

synchnoized與Lock的對比

  • Lock是顯式鎖(手動開啓和關閉鎖)
  • Lock只有代碼塊鎖
  • 使用Lock鎖,JVM將花費較少的時間來調度線程,性能更好、擴展型好(提供更多的子類)
  • synchonized是隱式鎖,出了作用域自動釋放
  • synchonized有代碼塊鎖和方法鎖

優先使用順序

  • Lock > 同步代碼塊(已經進入了方法體,分配了相應資源)> 同步方法(在方法體之外)

10. 線程通信 – 生產者和消費者問題

線程間通信方法

  • 均是Object類的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出異常:lllegalMonitorStateException
方法名 作用
wait() 一直等待,直到其他線程通知,與sleep不同,會釋放鎖
wait(long timeout) 指定等待的毫秒數
notify() 喚醒一個處於等待狀態的線程
notifyAll() 喚醒同一對象上所有調用wait方法的線程,優先級高的線程優點調度

併發協作模式”生產者消費者模式“

管程法 – 利用緩衝區解決

  • 生產者:負責生產數據的模塊(可能是方法、對象、線程、進程)
  • 消費者:負責處理數據的模塊(可能是方法、對象、線程、進程)
  • 緩衝區:消費者不能直接使用生產者的數據

生產者將生產好的數據放入緩衝區,消費者衝緩衝區中拿出數據

代碼:


信號燈法 – 標誌位

生產者:演員

class Actor extends Thread {
    Movie movie;

    public Actor(Movie movie) {
        this.movie = movie;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                this.movie.play("夏洛特煩惱");
            } else {
                this.movie.play("唐人街探案");
            }
        }
    }
}

消費者:觀衆

class watcher extends Thread {
    Movie movie;

    public watcher(Movie movie) {
        this.movie = movie;
    }

    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            movie.watch();
        }
    }
}

產品:電影

class Movie {
    /**
     *  拍攝電影時,觀衆等待
     */

    /**
     * 放映電影時,演員等待
     */

    /**
     * 表演的節目
     */
    String movie;
    /**
     * 標誌位
     */
    boolean flag = true;

    /**
     * 表演方法
     */
    public synchronized void play(String movie) {
        // 判斷演員等待
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演員表演了:" + movie);
        System.out.println("==========");
        // 通知觀衆觀看
        // 通知喚醒
        this.notifyAll();
        this.movie = movie;
        this.flag = !this.flag;

    }

    /**
     * 觀看
     */
    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀衆看了:" + movie);
        System.out.println("~~~~~~~~~~");
        // 通知演員表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

測試類:

public class TestPC2 {
    public static void main(String[] args) {
        Movie movie= new Movie();

        new Actor(movie).start();
        new watcher(movie).start();

    }
}

結果:

演員表演了:夏洛特煩惱
==========
觀衆看了:夏洛特煩惱
~~~~~~~~~~
演員表演了:唐人街探案
==========
觀衆看了:唐人街探案
~~~~~~~~~~
演員表演了:夏洛特煩惱
==========
觀衆看了:夏洛特煩惱
~~~~~~~~~~
演員表演了:唐人街探案
==========
觀衆看了:唐人街探案
~~~~~~~~~~

11. 線程池 【JDK5】

經常創建和銷燬使用量特別大的資源,如併發情況下的線程,對性能影響很大。

解決

  • 提前創建好多個線程,放入線程池中,使用時直接獲取,用完放回池中。

JDK5.0起提供了線程池相關API:ExecutorServiceExcecutors

ExecutoeService方法

ExecutoeService:真正的線程池接口,常見子類ThreadExecutor方法

  • 執行Runnable,任務/命令,無返回值
void execute(Runnable command);
  • 執行Callable接口,任務/命令,有返回值
<T> Future<T> sunbmit(Callable<T> task);

Excecutors工具類

線程池的工廠類,用於創建並返回不同類型的線程池

優點

  • 提高響應速度(減少了創建新線程的時間)
  • 降低資源消耗(重複利用線程池中的線程)
  • 便於線程管理
    • corePoolSize:核心池的大小
    • maxmumPooSize:最大線程數
    • keeoAliveTime:線程無任務是最多保持多長時間後會終止

使用步驟:

  1. 創建服務,創建線程池(參數爲線程池大小)

    ExecutorService service = Executors.newFixedThreadPool(10);
    
  2. 執行線程類,方法:

     // runable接口是execute方法
     service.execute(new MyThread());
     
     // callable接口是submit方法
     Future<Boolean> r1 = service.submit(c1);
    
  3. 關閉連接池

     service.shutdownNow();
    

代碼:

線程類

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

創建線程池

package cn.luis.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPool {

    public static void main(String[] args) {
        // 1.創建服務,創建線程池[參數爲線程池大小]
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 2.執行Runable接口實現類的線程【execute方法】,callable接口是submit方法
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        // 3.釋放連接
        service.shutdownNow();
    }
}

結果:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4

END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章