這份java多線程筆記,你真得好好看看,我還沒見過總結的這麼全面的

1.線程,進程和多線程

1.程序:指指令和數據的有序集合,其自己沒有任何意義,是一個靜態的概念
2.進程:指執行程序的一次執行過程,是一個動態的概念。是系統資源分配的單位(注意:不少多線程是模擬出來的,真正的多線程是指有多個cpu,即多核,如服務器。即在一個cpu的狀況下,在同一時間點,cpu只能執行一個代碼,由於切換的很快,因此就有同時執行的錯覺)
3.線程:一般一個進程中能夠包含若干個線程,一個進程中至少有一個線程。線程是cpu調度和執行的單位
4.並行:指在同一時刻,有多條指令在多個處理器上同時執行。因此不管從微觀仍是從宏觀來看,兩者都是一塊兒執行的。
5.併發:指在同一時刻只能有一條指令執行,但多個進程指令被快速的輪換執行,使得在宏觀上具備多個進程同時執行的效果,但在微觀上並非同時執行的,只是把時間分紅若干段,使多個進程快速交替的執行。java

核心概念web

  • 線程就是獨立的執行路徑
  • 在程序運行時,及時沒有本身建立線程,後臺也會有多個線程,如主線程,gc線程;
  • main()稱之爲主線程,爲系統的入口,用於執行整個程序;
  • 在一個進程中,若是開闢了多個線程,線程的行爲由調度器安排調度,調度器時與操做系統緊密相關的,前後順序時不能人爲的干預的。
  • 對同一份資源操做時,會存在資源搶奪的問題,須要加入併發控制;
  • 線程會帶來額外的開銷,如cpu調度事件,併發控制開銷。
  • 每一個線程在本身的工做內存交戶,內存控制不當會形成數據不一致。

2.線程建立

三種建立方式:express

  • Thread class====>繼承Thread類(重點)
  • Runnable接口====>實現Runnable接口(重點)
  • Callable接口====>實現Callable接口(瞭解)

Thread

  • 自定義線程類繼承Thread類
  • 重寫run()方法,編寫線程執行體
  • 建立線程對象,調用start()方法啓動線程
    不建議使用:避免OPP單繼承侷限性
//建立方式一:繼承Thread類,重寫run方法,調用start開啓線程

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 < 200; i++) {
            System.out.println("我在打飛機遊戲-------"+i);
        }
    }
}

最後會發現「我在打遊戲」和「我在打飛機遊戲」會交替執行。
注意:線程不必定當即執行,由cpu安排調度;編程

實現Runnable

推薦使用Runnable,避免單繼承的侷限性,方便同一個對象被多個線程使用安全

  • 定義MyRunnable類事項Runnable接口
  • 實現run()方法,編寫線程執行體
  • 建立線程對象,調用start()方法啓動線程
    買票案例
//票數
    //多個線程同時操做同一個對象
//買火車票

//發現問題,多個線程操做同一個資源的狀況下,線程不安全,數據紊亂
public class TestThread04 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) {
        TestThread04 ticket = new TestThread04();

        new Thread(ticket,"小明").start();
        new Thread(ticket,"老師").start();
        new Thread(ticket,"黃牛").start();
    }
}
老師拿到了第10票
小明拿到了第9票
黃牛拿到了第9票
老師拿到了第8票
小明拿到了第6票
黃牛拿到了第7票
黃牛拿到了第5票
老師拿到了第4票
小明拿到了第4票
黃牛拿到了第2票
小明拿到了第1票
老師拿到了第3票

能夠發現,多個線程操做同一資源可能存在併發性問題服務器

龜兔賽跑案例網絡

//模擬龜兔賽跑
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();
    }
}

實現Callable接口(瞭解便可)

  1. 實現Callable接口,須要返回值類型
  2. 重寫call方法,須要拋出異常
  3. 建立目標對象
  4. 建立執行服務:ExecutorService ser=Executors.newFixedThreadPool(1);
  5. 提交執行:Future result1 = ser.submit(t1);
  6. 獲取結果:boolean r1 = result1.get()
  7. 關閉服務:ser.shutdownNow();

下載案例多線程

//線程建立方式三:實現callable接口
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) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://i.guancha.cn/bbs/2020/07/10/20200710142345269.jpg?imageView2/2/w/500/format/jpg","1.jpg");
        TestCallable t2 = new TestCallable("https://i.guancha.cn/bbs/2020/07/10/20200710142345269.jpg?imageView2/2/w/500/format/jpg","2.jpg");
        TestCallable t3 = new TestCallable("https://i.guancha.cn/bbs/2020/07/10/20200710142345269.jpg?imageView2/2/w/500/format/jpg","3.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();

        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);

        //關閉服務
        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方法出現問題");
            }
        }
    }
}

3.Lamda表達式

  • λ希臘字母表中排序第十一爲的字母,英語名稱爲Lambda
  • 避免匿名內部類定義過多
  • 其實質屬於函數式編程的概念
  • jdk1.8後新增的一種技術
(params)->expression[表達式]
  (params)->statement[語句]
    (params)->{statements}

a->System.out.println("i like lambda-->"+a);

new Thread(()->System.out.println("多線程學習。。。")).start();

爲何要使用lambda表達式併發

  • 避免匿名內部類定義過多
  • 可讓你的代碼看起來很簡潔
  • 去掉了一堆沒有意義的代碼,只留下核心的邏輯

函數式接口的定義app

  • 任何接口,若是隻包含惟一一個抽象方法,那麼他就是一個函數式接口。

    public interface Runnable{
                public abstract void run();
        }
  • 對於函數式接口,咱們能夠經過lambda表達式來建立該接口的對象。

推到lambda表達式

/*
推到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簡化
        ILike like1 = ()->{
            System.out.println("i like lambda6");
        };
        like1.lambda();
    }
}

//1.定義一個函數式接口
interface ILike{
    void lambda();
}

//2.實現類
class Like implements ILike{

    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}

案列二

public class TestLambda2 {
    static class Love implements ILove{

        @Override
        public void love(int a) {
            System.out.println("i love u"+a);
        }
    }

    public static void main(String[] args) {
        //1.lambda表示簡化
        ILove love = (int a) ->{
                System.out.println("i love u"+a);
        };

        //簡化1,去掉參數類型
        love = (a)->{
            System.out.println("i love u"+a);
        };

        //簡化2,簡化括號
        love = a->{
            System.out.println("i love u"+a);
        };

        //簡化3,去掉花括號
        love = a -> System.out.println("i love u"+a);

        //總結:
            //lambda表達式只能有一行代碼的狀況下才能簡化成爲一行,若是有多行,那麼就用代碼塊包裹
            //前提接口爲函數式接口
            //多個參數也能夠去掉參數類型,要去掉就都去掉,必須加上括號

        love.love(521);

    }
}

interface ILove{
    void love(int a);
}

4.靜態代理

  • 你:真實角色
  • 婚慶公司:代理你,幫你處理結婚的事
  • 結婚:實現都實現結婚接口便可

案列

public class StaticProxy {

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

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

        new WeddingCompany(new You()).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 after() {
        System.out.println("結婚以後,收尾款");
    }

    private void before() {
        System.out.println("結婚以前,佈置現場");
    }
}

5.線程狀態

五大狀態

線程中止

setPriority(int newPriority): 更改線程的優先級

  • static void sleep(long millis): 在指定的毫秒數內讓當前正在執行的線程休眠
  • void join():等待該線程終止
  • static void yield():暫停當前正在執行的線程對象,並執行其餘線程
  • void interrupt():中斷線程,別用這個方式
  • boolean isAlive():測試線程是否處於活動狀態

1.不推薦使用JDK提供的stop(),destroy()方法。【已廢棄】

2.推薦線程本身中止下來

3.建議使用一個標誌位進行終止變量當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("線程該中止了");
            }
        }
    }
}

線程休眠

  • sleep(時間)指定當前線程阻塞的毫秒數
  • sleep存在異常InterruptedException
  • sleep時間達到後線程進入就緒狀態
  • sleep能夠模擬網絡延時,倒計時等。
  • 每個對象都有一個鎖,sleep不會釋放鎖;

用sleep方法模擬倒計時和打印當前時間

//模擬倒計時
public class TestSleep2 {

    public static void main(String[] args) {
        //打印當前系統時間
        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() throws InterruptedException {
        int num = 10;

        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }

}

線程禮讓

  • 禮讓線程,讓當前正在執行的線程暫停,但不阻塞
  • 將線程從運行狀態轉爲就緒狀態
  • 讓cpu從新調度,禮讓不必定成功!看cpu心情
//測試禮讓線程
//禮讓不必定成功,看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()+"線程中止執行");
    }
}

Join

  • Join合併線程,待此線程執行完成後,再執行其餘線程,其餘線程阻塞
  • 能夠想象成插隊
//測試join方法//想象爲插隊
public class TestJoin  implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("線程vip來了"+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 < 500; i++) {
            if (i == 200){
                thread.join();//插隊
            }
            System.out.println("main:"+i);
        }
    }
}

線程狀態觀測

Thread.State
線程狀態。線程能夠處於如下狀態之一:

  • NEW:還沒有啓動的線程處於此狀態
  • RUNNABLE:在Java虛擬機中執行的線程處於此狀態。
  • BLOCKED:被阻塞等待監視器鎖定的線程處於此狀態
  • WAITING:正在等待另外一個線程執行特定動做的線程處於此狀態
  • TIMED_WAITING:正在等待另外一個線程執行動做達到指定等待時間的線程處於此狀態
  • TERMINATED:已退出的線程處於此狀態

一個線程能夠在給定的時間點處於一個狀態。這些狀態是不反映任何操做系統線程狀態的虛擬機狀態。

//觀察測試線程的狀態
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();
        System.out.println(state);//NEW

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

        while (state != Thread.State.TERMINATED){//只要線程不終止,就一直輸出狀態
            Thread.sleep(100);
            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)

優先級的設定建議再start()調度前
優先級低知識意味着得到調度的機率低,並非優先級低就不會被調用了,這都是看cpu的調度。

6.守護(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============");
    }
}

7.線程安全

線程同步

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

線程同步:

  • 現實生活中,咱們會遇到「同一個資源,多我的都想使用」的問題,好比,食堂排隊打飯,每一個人都想吃飯,最自然的解決辦法就是,排隊,一個個來。
  • 處理多線程問題是,多個線程訪問同一個對象,而且某些線程還想修改這個對象,着時候咱們就須要線程同步,線程同步其實就是一種等待機制,多個須要同時訪問此對象的線程進入這個對象的等待池造成隊列,等待前面線程使用完畢,下一個線程再使用

造成條件:隊列+鎖

鎖:

  • 因爲同一進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也帶來了訪問衝突問題,爲了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized,當一個線程得到對象的排它鎖,獨佔資源,其餘線程必須等待,使用後釋放鎖便可,存在如下問題:

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

三大線程不安全案例

買票不安全

//不安全的買票
//線程不安全,有負數
public class UnsafeByyTicket {

    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;
    private boolean flag = true;//外部中止方式

    @Override
    public void run() {
        //買票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private 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 girlFriend = new Drawing(account,100,"girlFriend");

        you.start();
        girlFriend.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);
//        System.out.println(this.getName()+Thread.currentThread().getName());
        System.out.println(this.getName()+"手裏的錢"+nowMoney);
    }
}

不安全的集合

//線程不安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(5000);
        System.out.println(list.size());
    }
}

同步方法(synchronized)

  • 因爲咱們能夠經過private關鍵字來保證數據對象只能被方法訪問,因此咱們只須要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊
    同步方法:public synchronized void method(int args){}
  • synchronized方法控制對「對象」的訪問,每一個對象對應一把鎖,每一個synchronized方法都必須得到調用該方法的對象的鎖才能執行,不然線程會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的線程才能得到這個鎖,繼續執行
    缺陷:若將一個大的方法申明爲synchronized將會影響效率

    同步塊

    • 同步塊:synchronized (Obj){}
    • Obj稱之爲同步監視器

      • Obj能夠是任何對象,可是推薦使用共享資源做爲同步監視器
      • 同步方法中無需指定同步監視器,由於同步方法的同步監視器就是this,就是這個對象自己,或者是class[反射中講解]
    • 同步監視器的執行過程

      1. 第一個線程訪問, 鎖定同步監視器,執行其中代碼
      2. 第二個線程訪問,發現同步監視器被鎖定,沒法訪問。
      3. 第一個線程訪問完畢,解鎖同步監視器。
      4. 第二個線程訪問,發現同步監視器沒有鎖,而後鎖定並訪問。
### 安全的案例
//不安全的取錢
//兩我的去銀行取錢,帳戶
public class UnsafeBank {

    public static void main(String[] args) {
        Account account = new Account(1000,"結婚基金");

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

        you.start();
        girlFriend.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);
//        System.out.println(this.getName()+Thread.currentThread().getName());
            System.out.println(this.getName()+"手裏的錢"+nowMoney);
        }

    }
}
//不安全的買票
//線程不安全,有負數
public class UnsafeByyTicket {

    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;
    private 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;
        }

        //買票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}
//線程不安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}

JUC安全類型的集合

//測試JUC安全類型的集合
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<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());
    }
}

死鎖

  • 多個線程各自佔有一些共享資源,而且互相等待其餘線程佔有的資源才能運行,而致使兩個或者多個線程都在等待對方釋放資源,都中止執行的狀況,某一個同步塊同時擁有「兩個以上對象的鎖」時,就可能會發生「死鎖」的問題

死鎖避免方法

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

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

上面列出了死鎖的四個必要條件,咱們只要想辦法破解其中的任意一個或多個條件就能夠避免死鎖發生

8.Lock(鎖)

  • 從JDK5.0開始,Java提供了更強大的線程同步機制——經過顯示定義同步鎖對象來實現同步。同步鎖使用 Lock對象充當
  • java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源以前應先得到Lock對象。
  • ReentrantLock類實現了Lock,它擁有與synchronized相同的併發性和內存語義,在實現線程安全的控制中,比較經常使用的是ReentrantLock,能夠顯示加鎖,釋放鎖。

synchronized與Lock的對比

  • Lock是顯式鎖(手動開啓和關閉鎖,別忘記關閉鎖)synchronized是隱式鎖,出了做用域自動釋放
  • Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
  • 使用Lock鎖,JVM將花費較少的時間來調度線程,性能更好。而且具備更好的擴展性(提供更多的子類)
  • 優先使用順序

    • Lock > 同步代碼塊(已經進入了方法體,分配了相應資源)> 同步方法(在方法體以外)
//測試Lock鎖
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 = 1000;

    //定義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(Thread.currentThread().getName()+" "+ticketNums--);
            }else {
                break;
            }
            }finally {
                lock.unlock();
            }

        }
    }
}

9.線程協做

生產者消費者模式

  • 應用場景:生產者和消費者問題

    • 假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費。
    • 若是倉庫中沒有產品,則生產者將產品放入倉庫,不然中止生產並等待,直到倉庫中的產品被消費者取走爲止
    • 若是倉庫中放有產品,則消費者能夠將產品取走消費,不然中止消費並等待,直到倉庫中再次放入產品爲止。

線程通訊-分析

這是一個線程同步問題,生產者和消費者共享同一個資源,而且生產者和消費者之間相互依賴,互爲條件

  • 對於生產者,沒有生產產品以前,要通知消費者等待,而生產了產品以後,又須要立刻通知消費者消費
  • 對於消費者,在消費以後,要通知生產者已經結束消費,須要生產新的產品以供消費。
  • 在生產者消費者問題中,僅有synchronized是不夠的

    • synchronized可阻止併發更新同一個共享資源,實現了同步
    • synchronized不能用來實現不一樣線程之間的消息傳遞(通訊)

線程通訊

  • Java提供了幾個方法解決線程之間的通訊問題

方法名

做用

wait()

表示線程一直等待,直到其餘線程通知,與sleep不一樣,會釋放鎖

wait(long timeout)

指定等待的毫秒數

notify()

喚醒一個處於等待狀態的線程

notifyAll()

喚醒同一個對象上全部調用wait()方法的線程,優先級別高的線程優先調度

均是Object類的方法,都只能在同步方法或者同步代碼塊中使用,不然會拋出異常IllegalMonitorStateException

解決方式1

併發協做模型「生產者/消費者模式」—>管程法

  • 生產者:負責生產數據的模塊(多是方法,對象,線程,進程)
  • 消費者:負責處理數據的模塊(多是方法,對象,線程,進程)
  • 緩衝區:消費者不能直接使用生產者的數據,他們之間有個「緩衝區」
    生產者將生產好的數據放入緩衝區,消費者從緩衝區拿出數據
//測試:生產者消費者模型--》利用緩衝區解決:管程法

//生產者,消費者,產品,緩衝區
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){
        //若是容器滿了,就須要等待消費者消費
        if (count == chickens.length){
            //通知消費者消費,生產者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //若是沒有滿,咱們就須要丟入產品
        chickens[count] = chicken;
        count++;

        //能夠通知消費者消費了
        this.notifyAll();
    }

    //消費者消費產品
    public synchronized Chicken pop(){
        //判斷可否消費
        if (count == 0){
            //等待生產者生產,消費者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //若是能夠消費
        count--;
        Chicken chicken = chickens[count];

        //吃完了,通知生產者生產
        this.notifyAll();
        return chicken;
    }
}

解決方式2

  • 併發協做模型「生產者/消費者模式」–>信號燈法
package com.kuang.gaoji;

//測試生產者消費者問題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!= true){
            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 == true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀衆觀看了"+voice);
        //通知演員表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

10.線程池

  • 背景:常常建立和銷燬、使用量特別大的資源,好比並髮狀況下的線程,對性能影響很大。
  • 思路:提早建立好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。能夠避免頻繁建立銷燬、實現重複利用。相似生活中的公共交通工具。
  • 好處:

    • 提升響應速度(減小了建立新線程的時間)
    • 下降資源消耗(重複利用線程池中線程,不須要每次都建立)
    • 便於線程管理(…)

      • corePoolSize:線程池的大小
      • maximumPoolSize:最大線程數
      • keepAliveTime:線程沒有任務時最多保持多長時間後會終止。

使用線程池

  • JDK5.0起提供了線程池相關API:ExecutorService和Executors
  • ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor

    • void execute(Runnable command):執行任務/命令,沒有返回值,通常用來執行Runnable
    • Futuresubmit(Callabletask):執行任務,有返回值,通常用來執行Callable
    • void shutdown():關閉線程池
  • 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());
    }
}

11.總結

//回顧總結線程的建立
public class ThreadNew {
    public static void main(String[] args) {
        new MyThread1().start();

        new Thread(new MyThread2()).start();

        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
        new Thread(futureTask).start();

        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

//1.繼承Thread類
class MyThread1 extends Thread{
    @Override
    public void run(){
        System.out.println("MyThread1");
    }
}

//2.實現Runnable接口
class MyThread2 implements Runnable{

    @Override
    public void run() {
        System.out.println("MyThread2");
    }
}

//3.實現Callable接口
class MyThread3 implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {

        System.out.println("MyThread3");
        return 100;
    }
}

最後

感謝你看到這裏,看完有什麼的不懂的能夠在評論區問我,以爲文章對你有幫助的話記得給我點個贊,天天都會分享java相關技術文章或行業資訊,歡迎你們關注和轉發文章!

相關文章
相關標籤/搜索