多線程基礎部分

2、線程、進程、多線程:

程序、進程、線程的關係
程序是靜態的,程序跑起來成爲進程,進程是系統分配資源的單位,進程包含若干個線程,一個進程至少包含一個線程,否則沒有存在的意義
一個進程內的線程之間是可以共享資源的。
線程:線程也存在併發、並行(單個CPU時間片輪轉、一個時間點,多個CPU上的真同時)
一些概念:
線程就是獨立的執行路徑
在程序運行時,即使沒有自己創建線程,後臺也會有多個線程,如主線程、GC線程;
main()稱之爲主線程,是系統的入口,用於執行整個程序;
在一個進程中,如果開闢了多個線程,線程的運行由調度器安排調度,調度器是與操作系統密切相關的,先後順序是不能人爲干預的;
對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制;
線程會帶來額外的開銷,如cpu調度時間,併發控制開銷;
每個線程在自己的工作內存交互,內存控制不當會造成數據不一致。

3、繼承Thread類

創建線程的三種方式
在這裏插入圖片描述
創建線程方式一
繼承Thread類,重寫run()方法,調用start開啓線程
總結:線程開啓不一定立即執行,有CPU調度執行

不建議使用繼承Thread類:避免OOP單繼承侷限性

public class TestThread1 extends Thread{
    @Override
    public void run() {
        //run方法線程體
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代碼----"+ i);
        }
    }

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

        //創建線程對象
        TestThread1 testThread1 = new TestThread1();

        //調用start()方法開啓線程
        testThread1.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("我在學習多線程-------"+ i);
        }
    }
}

在這裏插入圖片描述

4、網圖下載

package com.fang.demo01;

import org.apache.commons.io.FileUtils;

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

//練習Thread,實現多線程同步下載圖片
public class TestThread2 extends Thread{
    private String url;         //網絡圖片地址
    private String name;        //保存的文件名

    public TestThread2(String url, String name){
        this.url = url;
        this.name = name;
    }

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

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://i0.hdslb.com/bfs/sycp/creative_img/202006/6d586392ee5c3b537d5b5c832fc02c19.jpg","1.jpg");
        TestThread2 t2 = new TestThread2("https://i0.hdslb.com/bfs/sycp/creative_img/202002/6bc0da8a84ece9e200fce8b7ec8b08c8.png","2.png");
        TestThread2 t3 = new TestThread2("https://i0.hdslb.com/bfs/sycp/creative_img/202006/dde761ef0291bc064d8ba955e0b266a7.jpg","3.jpg");

        t1.start();
        t2.start();
        t3.start();
    }
}
//下載器
class WebDownloader{
    //下載方法
    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異常,downloader方法出現問題");
        }
    }
}

5、實現Runnable

定義MyRunnable類實現Runnable接口
實現run()方法,編寫線程執行體
創建線程對象,調用start()方法啓動線程

創建線程方式二:

推薦使用:避免單繼承侷限性,靈活方便,方便同一個對象被多個線程使用

package com.fang.demo01;

public class TestThread3 implements Runnable{
    @Override
    public void run() {
        //run方法線程體
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代碼----"+ i);
        }
    }

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

        //創建Runnable接口的實現類對象
        TestThread3 testThread3 = new TestThread3();

        //創建線程對象,調用start()方法開啓線程
        new Thread(testThread3).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("我在學習多線程-------"+ i);
        }
    }
}

6、初識併發問題

package com.fang.demo01;
//多個線程同時操作同一個對象
//買火車票的例子
public class TestThread4 implements Runnable{
    //票數
    private int ticketNums = 10;

    @Override
    public void run() {
        while(true){
            if(ticketNums <= 0){
                break;
            }
            //模擬延時
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums-- +"張票");
        }
    }

    public static void main(String[] args) {
        TestThread4 tiket = new TestThread4();

        new Thread(tiket,"小明").start();
        new Thread(tiket,"老師").start();
        new Thread(tiket,"黃牛黨").start();
    }
}

在這裏插入圖片描述
出現了安全問題

7、模擬龜兔賽跑

在這裏插入圖片描述

package com.fang.demo01;
//模擬龜兔賽跑
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(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判斷比賽是否結束
            boolean flag = gameOver(i);
            //如果比賽結束了,就停止程序
            if(flag)
                break;

            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }
    //判斷是否完成比賽
    private boolean gameOver(int steps){
        //判斷是否有勝利者
        if(winner != null){//已經存在勝利者了
            return true;
        }{
            if(steps >= 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();
    }
}

8、實現Callable接口(瞭解)

創建線程方式三:實現callable接口

callable的好處
1、可以定義返回值
2、可以拋出異常
在這裏插入圖片描述

package com.fang.demo02;

import com.fang.demo01.TestThread2;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestCallable implements Callable<Boolean> {
    private String url;         //網絡圖片地址
    private String name;        //保存的文件名

    public TestCallable(String url, String name){
        this.url = url;
        this.name = name;
    }

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

    public static void main(String[] args) {
        TestCallable t1 = new TestCallable("https://i0.hdslb.com/bfs/sycp/creative_img/202006/6d586392ee5c3b537d5b5c832fc02c19.jpg","1.jpg");
        TestCallable t2 = new TestCallable("https://i0.hdslb.com/bfs/sycp/creative_img/202002/6bc0da8a84ece9e200fce8b7ec8b08c8.png","2.png");
        TestCallable t3 = new TestCallable("https://i0.hdslb.com/bfs/sycp/creative_img/202006/dde761ef0291bc064d8ba955e0b266a7.jpg","3.jpg");

        //創建執行服務
        ExcutorService ser = Executors.newFixedThreadPool(3);

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

        //獲取結果
        boolean ret1 = r1.get();
        boolean ret2 = r2.get();
        boolean ret3 = r3.get();

        //關閉服務
        ser.shutdownNow();
    }
}
//下載器
class WebDownloader{
    //下載方法
    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異常,downloader方法出現問題");
        }
    }
}

9、Labmda表達式

理解Functional Iterface(函數式接口)是學習Java8 Lambda表達式的關鍵所在
函數式接口定義
任何接口,如果只包含唯一一個抽象方法,那麼他就是一個函數式接口。
對於函數式接口,我們可以通過lambda表達式來創建該接口的對象

public interface Runnable{
	public abstract void run();	
}		

在這裏插入圖片描述

看一段代碼,推導lambda表達式,其中1~6逐步優化,最後一個使用lambda表達式

public class TestLambda1 {

    //3、靜態內部類
    static class Like2 implements ILike{
        //重寫方法:Alt + Insert建
        @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{
    //重寫方法:Alt + Insert建
    @Override
    public void lambda() {
        System.out.println("I like lambda");
    }
}

爲什麼要用Lambda表達式
避免匿名內部類定義過多
讓代碼簡潔,去掉無意義代碼,留下核心邏輯

public class TestLambda2 {
    public static void main(String[] args) {
        ILove love = null;
//        //1、lambda表達式
//        ILove love = (String s)->{
//                System.out.println("I love you--->"+ s);
//        };
//        //2、參數類型
//        love = (s) -> System.out.println("I love you--->"+ s);

        //3、簡化括號()和{}都可以簡化
        // 如果代碼有多行就不能簡化花括號{ }
        love = s -> System.out.println("I love you--->"+ s);
        //接口=(參數)->方法體

        //總結
            // lambda表達式只有一行代碼時纔可以簡化爲一行,如果有多行,就要用代碼塊包裹
            // 前提是接口爲函數式接口
            // 多個參數也可以去掉參數類型,要去掉就要都去掉,但必須加上圓括號( )
        love.love("WYQ");
    }
}

interface ILove{
    void love(String s);
}

10、靜態代理模式

實現靜態代理對比Thread

靜態代理模式總結
// 真實對象和代理對象都要事先同一個接口
// 代理對象要代理真實角色

好處:
// 代理對象可以做很多真實對象坐不了的事情
// 真實對象專注做自己的事情

Thread線程的底部的實現原理:
// 下面的HappMarry()方法就相當於線程裏面的start()方法
// Thread代理了Runnable接口
// 同樣,婚慶公司代理了Marry接口,它們的共同方法叫HappyMarry()

public class StaticProxy {
    public static void main(String[] args) {
        You you = new You();//你要結婚

        new Thread(()-> System.out.println("我愛你")).start();

        new WeddingCompany(new You()).HappyMarry();

//        WeddingCompany weddingCompany = new WeddingCompany(you);
//        weddingCompany.HappyMarry();

    }
}

interface Marry{
    void HappyMarry();
}

//真實的你去結婚
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("阿芳要結婚了,超級開心");
    }
}
//婚慶公司代理,幫助你結婚
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 before(){
        System.out.println("結婚之前,佈置現場");
    }
    private void after(){
        System.out.println("結婚之後,收尾款");
    }
}

11、線程的狀態

在這裏插入圖片描述
線程方法
在這裏插入圖片描述
停止線程
不建議使用JDK提供的stop()、destroy()方法,已廢棄
推薦線程自己停止下來
建議使用一個標誌位進行終止變量,當flag=false,則終止線程運行。

我們的思路:使用一個標誌位,自己寫一個stop方法

//測試線程停止
//1、建議線程正常停止--->利用次數,不建議死循環
//2、建議使用標誌位--->設置一個標誌位
//3、不要使用stop或者destroy等過時的或者JDK不建議使用的方法
public class TestStop implements Runnable{
    //1、設置一個標誌位
    private boolean flag = true;

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

    //2、設置一個公開的方法停止線程,轉換標誌位
    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 < 1000; i++) {
            System.out.println("main" + i);
            if(i == 900) {
                //調用stop方法切換標誌位,停止線程
                testStop.stop();
                System.out.println("線程該停止啦·");
            }
        }
    }
}

12、線程休眠

在這裏插入圖片描述
獲取系統當前時間

package com.fang.demo03;/*
 * @Program:untitled
 * @Description:description
 * @Author:Pufang
 * @Time:2020-06-18 19-24-03
 **/

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

public class TestSleep2 {

    public static void main(String[] args) {
		//模擬倒計時
		//tendown();
		
        //打印當前系統時間
        Date startTime = new Date(System.currentTimeMillis());//獲取系統時間

        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = new Date(System.currentTimeMillis());//更新時間
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //模擬倒計時
    public static void tenDown(){
        int num = 10;
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(num--);
            if(num <= 0){
                break;
            }
        }
    }
}

13、線程禮讓yield()

在這裏插入圖片描述

//測試禮讓線程,禮讓不一定成功,看CPU心情(調度)
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()+"線程結束");
    }
}

14、線程強制執行join()

Join合併線程,待此線程執行完畢後,其他線程再執行, 其他線程阻塞
可以想象成打飯插隊

//測試join方法---想象爲插隊
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("VIP大哥來了"+i);
        }
    }

    public static void main(String[] args) {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        for (int i = 0; i < 500; i++) {
            if(i == 200){
                try {
                    thread.join();//插隊
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main"+i);
        }
    }
}

15、觀察線程狀態Thread.State

在這裏插入圖片描述

//測試觀察線程狀態
public class TestState {

    public static void main(String[] args) {
        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();
        System.out.println(state);//NEW

        //觀察啓動後
        thread.start();
        state = thread.getState();
        System.out.println(state);//RUNNABLE

        //只要線程不終止,就一直輸出狀態
        while(state != Thread.State.TERMINATED){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            state = thread.getState();//更新線程狀態
            System.out.println(state);//TERMINATED
        }
        thread.start();//一旦線程死亡就不能再次啓動了,一個線程只能死亡一次
    }
}

16、線程優先級

在這裏插入圖片描述

//測試線程優先級
public class TestPriority{
    public static void main(String[] args) {
        //主線程優先級默認爲5
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

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

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

        t2.setPriority(1);
        t2.start();

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

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();

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

        t6.setPriority(7);
        t6.start();
    }
}

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

17、守護線程(daemon)

線程分爲用戶線程守護線程
虛擬機必須確保用戶線程執行完畢
虛擬機不用等待守護線程執行完畢

如,後臺記錄操作日誌,監控內存,垃圾回收等

//測試守護線程
//上帝守護你
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);//默認是false表示用戶線程,正常的線程都是用戶線程

        thread.start();//上帝守護線程啓動了

        new Thread(you).start();//你啓動了
    }
}

//上帝
class God implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("上帝保護着你");
        }
    }
}
//你
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都開心的活着");
        }
        System.out.println("----goodbye! world-----");
    }
}

18、線程同步機制

多個線程操作同一個資源
併發:同一個對象被多個線程同時操作

線程同步需要:隊列+鎖
在這裏插入圖片描述
在這裏插入圖片描述

19、三大不安全案例

每個線程在自己的工作內存交互,內存控制不當會造成數據不一致

//線程不安全,有負數
//每個線程在自己的工作內存交互,內存控制不當會造成數據不一致
public class UnsafeTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"苦逼的我").start();
        new Thread(station,"牛逼的你們").start();
        new Thread(station,"可惡的黃牛黨").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums = 10;
    boolean flag = true;
    @Override
    public void run() {
        while(flag){
            buy();
        }
    }
    private void buy(){
        //判斷是否有票
        if(ticketNums <= 0){
            flag = false;
            return;
        }
        //模擬延時
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //買票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}
public class UnsafeBank {
    public static void main(String[] args) {
        //賬戶
        Account account = new Account(100, "結婚基金");
        Drawing you = new Drawing(account, 50, "你");
        Drawing GF = new Drawing(account, 100, "GF");
        you.start();
        GF.start();
    }
}
class Account{
    int money;//餘額
    String name;//卡名
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//銀行:模擬取款
class Drawing extends Thread{
    Account account;//賬戶
    //取了多少錢
    int drawingMoney;
    //現在手裏有多少錢
    int nowMoney;
    public Drawing(Account account, int drawingMoney, String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    @Override
    public void run() {
        //判斷有沒有錢
        if(account.money - drawingMoney < 0){
            System.out.println(Thread.currentThread().getName()+"錢不夠,取不了");
            return;
        }
        //sleep可以放大問題的發生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //卡內餘額 = 餘額 - 你取的前
        account.money = account.money - drawingMoney;
        //你手裏的錢
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name+"餘額爲"+account.money);
        //this.getName() 等價於 Thread.currentThread().getName();
        System.out.println(this.getName()+"手裏的錢"+nowMoney);
    }
}
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

20、同步方法及同步塊

先來看下利用synchronized實現同步的基礎:Java中的每一個對象都可以作爲鎖。具體表現
爲以下3種形式。
·對於普通同步方法,鎖是當前實例對象。
·對於靜態同步方法,鎖是當前類的Class對象。
·對於同步方法塊,鎖是Synchonized括號裏配置的對象。

當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
對上面三個不安全的案例進行加鎖

//每個線程在自己的工作內存交互,內存控制不當會造成數據不一致
public class UnsafeTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"苦逼的我").start();
        new Thread(station,"牛逼的你們").start();
        new Thread(station,"可惡的黃牛黨").start();
    }

}
class BuyTicket implements Runnable{
    //票
    private int ticketNums = 10;
    boolean flag = true;
    @Override
    public void run() {
        while(flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //synchronized默認鎖的是this.    //-----------------------------------------------------加鎖變爲安全的
    private synchronized void buy() throws InterruptedException{
        //判斷是否有票
        if(ticketNums <= 0){
            flag = false;
            return;
        }
        //模擬延時
            Thread.sleep(100);
        //買票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}
public class UnsafeBank {
    public static void main(String[] args) {
        //賬戶
        Account account = new Account(100, "結婚基金");

        Drawing you = new Drawing(account, 50, "你");
        Drawing GF = new Drawing(account, 100, "GF");

        you.start();
        GF.start();
    }
}
class Account{
    int money;//餘額
    String name;//卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//銀行:模擬取款
class Drawing extends Thread{
    Account account;//賬戶
    //取了多少錢
    int drawingMoney;
    //現在手裏有多少錢
    int nowMoney;
    public Drawing(Account account, int drawingMoney, String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;

    }
    //取錢
    //synchronized默認鎖的是this.
    @Override
    public void run() {
        //這裏鎖的對象是變化的量,需要增刪改的對象
        synchronized (account){//-----------------------------------------------------加鎖變爲安全的
            //判斷有沒有錢
            if(account.money - drawingMoney < 0){
                System.out.println(Thread.currentThread().getName()+"錢不夠,取不了");
                return;
            }
            //sleep可以放大問題的發生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡內餘額 = 餘額 - 你取的前
            account.money = account.money - drawingMoney;
            //你手裏的錢
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name+"餘額爲"+account.money);
            //this.getName() 等價於 Thread.currentThread().getName();
            System.out.println(this.getName()+"手裏的錢"+nowMoney);
        }
    }
}
import java.util.ArrayList;
import java.util.List;

public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){//-----------------------------------------------------加鎖變爲安全的
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

21、CopyOnWriteArrayList

併發安全的list

import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add((Thread.currentThread().getName()));
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

22、死鎖

在這裏插入圖片描述

//死鎖:多個線程互相抱着對方需要的資源,互相僵持
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0, "灰姑娘");
        Makeup g2 = new Makeup(1, "白雪公主");
        g1.start();
        g2.start();
    }
}
//口紅
class Lipstick{
}
//鏡子
class Mirror{
}
class Makeup extends Thread{
    //需要的資源只有一份,用static來保證只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    
    int choice;//選擇
    String girlName;//使用化妝工具的人
    
    Makeup(int choice, String girlName){
        this.choice = choice;
        this.girlName = girlName;
    }
    @Override
    public void run() {
        //化妝
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //化妝,互相持有對方的鎖,需要拿到對方的資源
    private void makeup() throws InterruptedException {
        if(choice == 0){
            synchronized (lipstick){//獲得口紅的鎖
                System.out.println(this.girlName+"獲得口紅的鎖");
                Thread.sleep(1000);
                synchronized (mirror){//一秒後想獲得鏡子
                    System.out.println(this.girlName+"獲得鏡子的鎖");
                }
            }
            //把下面這個synchronized塊寫在外面就不會死鎖
//            synchronized (mirror){//一秒後想獲得鏡子
//                System.out.println(this.girlName+"獲得鏡子的鎖");
//            }
        }else{
            synchronized (mirror){//獲得鏡子的鎖
                System.out.println(this.girlName+"獲得鏡子的鎖");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.girlName+"獲得口紅的鎖");
                }
            }
            //把下面這個synchronized塊寫在外面就不會死鎖
//            synchronized (lipstick){
//                System.out.println(this.girlName+"獲得口紅的鎖");
//            }
        }
    }
}

在這裏插入圖片描述

23、Lock(鎖)

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

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
class TestLock2 implements Runnable{
    int ticketNums = 10;

    //定義Lock鎖
    private  final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){

            try {
                lock.lock();
                if(ticketNums > 0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else{
                    break;
                }
            }finally {
                //解鎖
                lock.unlock();
            }
        }
    }
}

24、生產者消費者問題

線程通信
在這裏插入圖片描述
在這裏插入圖片描述

25、管程法

在這裏插入圖片描述

//生產者消費者模型-利用緩衝區解決--管程法

//生產者  消費者  產品  緩衝區
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

//生產者
class Productor extends Thread{
    SynContainer container;
    public Productor(SynContainer container){
        this.container = container;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生產了"+i+"只雞");
        }
    }
}

//消費者
class Consumer extends Thread{
    SynContainer container;
    public Consumer(SynContainer container){
        this.container = container;
    }
    //消費
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消費了-->"+container.pop().id+"只雞");
        }
    }
}

//產品
class Chicken{
    int id;//編號
    public Chicken(int id) {
        this.id = id;
    }
}

//緩衝區
class SynContainer{
    //容器大小
    Chicken[] chickens = new Chicken[10];
    //容器計數器
    int count = 0;
    //生產者放入產品
    public synchronized void push(Chicken chicken){
        //如果滿了,就需要等待消費者消費
        while(count == chickens.length){
            //通知消費者消費,生產等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果沒有滿,就需要放入產品
        chickens[count] = chicken;
        count++;

        //可以通知消費者消費了
        this.notifyAll();
    }
    //消費者消費產品
    public synchronized Chicken pop(){
        //判斷能否消費
        while(count == 0){
            //等待生產者生產,消費者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消費
        count--;
        Chicken chicken = chickens[count];
        //喫完了,通知生產者生產
        this.notifyAll();
        return chicken;
    }
}

26、信號燈法

在這裏插入圖片描述

//測試生產者消費者問題2:信號燈法,標誌位解決
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

//生產者---演員
class Player extends Thread{
    TV tv;
    public Player(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i % 2 == 0){
                this.tv.play("快樂大本營播放中");
            }else{
                this.tv.play("抖音記錄美好生活");
            }
        }
    }
}
//消費者---觀衆
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}
//產品---節目
class TV{
    //演員表演,觀衆等待
    //觀衆觀看,演員等待
    String voice;//表演的節目
    boolean flag = true;

    //表演
    public synchronized void play(String voice){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演員表演了"+voice);
        //通知觀衆觀看
        this.notifyAll();//通知喚醒
        this.voice = voice;
        this.flag = !this.flag;
    }
    //觀看
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀看了"+voice);
        this.notifyAll();
        this.flag = !this.flag;
    }
}

27、線程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
    public static void main(String[] args){
        //1、創建服務,創建線程池
        //newFixedThreadPool 參數爲:線程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        //執行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //2、關閉連接
        service.shutdown();
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}

在這裏插入圖片描述

28、總結

一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭情況逐漸升級。鎖可以升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提高獲得鎖和釋放鎖的效率。
**無鎖:**沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功,其他修改
失敗的線程會不斷重試直到修改成功。
**偏向鎖:**對象的代碼一直被同一線程執行,不存在多個線程競爭,該線程在後續的執行中自動獲取鎖,降低獲取鎖
帶來的性能開銷。偏向鎖,指的就是偏向第一個加鎖線程,該線程是不會主動釋放偏向鎖的,只有當其他線程嘗試
競爭偏向鎖纔會被釋放。
偏向鎖的撤銷,需要在某個時間點上沒有字節碼正在執行時,先暫停擁有偏向鎖的線程,然後判斷鎖對象是否處於
被鎖定狀態。如果線程不處於活動狀態,則將對象頭設置成無鎖狀態,並撤銷偏向鎖;
如果線程處於活動狀態,升級爲輕量級鎖的狀態。
**輕量級鎖:**輕量級鎖是指當鎖是偏向鎖的時候,被第二個線程 B 所訪問,此時偏向鎖就會升級爲輕量級鎖,線程 B
會通過自旋的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。
當前只有一個等待線程,則該線程將通過自旋進行等待。但是當自旋超過一定的次數時,輕量級鎖便會升級爲重量
級鎖;當一個線程已持有鎖,另一個線程在自旋,而此時又有第三個線程來訪時,輕量級鎖也會升級爲重量級鎖。
**重量級鎖:**指當有一個線程獲取鎖之後,其餘所有等待獲取該鎖的線程都會處於阻塞狀態。
重量級鎖通過對象內部的監視器(monitor)實現,而其中 monitor 的本質是依賴於底層操作系統的 Mutex Lock
實現,操作系統實現線程之間的切換需要從用戶態切換到內核態,切換成本非常高

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