day33-線程基礎03

来源:https://www.cnblogs.com/liyuelian/archive/2022/09/05/16659594.html
-Advertisement-
Play Games

線程基礎03 6.用戶線程和守護線程 用戶線程:也叫工作線程,當線程的任務執行完或者通知方法結束。平時用到的普通線程均是用戶線程,當在Java程式中創建一個線程,它就被稱為用戶線程 守護線程(Daemon):一般是為工作線程服務的,當所有的用戶線程結束,守護線程自動結束 常見的守護線程:垃圾回收機制 ...


線程基礎03

6.用戶線程和守護線程

  1. 用戶線程:也叫工作線程,當線程的任務執行完或者通知方法結束。平時用到的普通線程均是用戶線程,當在Java程式中創建一個線程,它就被稱為用戶線程

  2. 守護線程(Daemon):一般是為工作線程服務的,當所有的用戶線程結束,守護線程自動結束

  3. 常見的守護線程:垃圾回收機制

例子1:如何將一個線程設置成守護線程

package li.thread.method;

public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        
        //如果我們希望當主線程結束後,子線程自動結束,只需要將子線程設置為守護線程
        myDaemonThread.setDaemon(true);
        
        myDaemonThread.start();
        
        for (int i = 1; i <= 10; i++) {//main線程
            System.out.println("悟空在前方打妖精...");
            Thread.sleep(1000);
        }
    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        for (; ; ) {//無限迴圈
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("八戒收拾東西回高老莊...");
        }
    }
}
image-20220905114351151

7.線程的生命周期

  • JDK中用Thread.State枚舉表示了線程的幾種狀態:

image-20220905115805705

202209051242

例子

package li.thread.state;

public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + "狀態 " + t.getState());
        t.start();
        while (t.getState() != Thread.State.TERMINATED) {
            System.out.println(t.getName() + "狀態 " + t.getState());
            Thread.sleep(1000);
        }

        System.out.println(t.getName() + "狀態 " + t.getState());

    }
}

class T extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("hi" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

8.線程同步機制

  • 線程同步機制
  1. 在多線程編程中,一些敏感數據不允許被多個線程同時訪問,此時就使用同步訪問技術,保證數據在任何同一時刻,最多有一個線程訪問,以保證數據的完整性。
  2. 也可以理解為:線程同步,即當有一個線程在對記憶體進行操作時,其他線程都不可以對這個記憶體地址進行操作,直到該線程完成操作,其他線程才能對該記憶體地址進行操作。
  • 同步具體方法--Synchronized
  1. 同步代碼塊

    synchronized(對象){//得到對象的鎖,才能操作同步代碼
    	//需要被同步的代碼
    }
    
  2. synchronized還可以放在方法聲明中,表示整個方法為同步方法

    public synchronized void m(String name){
        //需要被同步的代碼
    }
    

    就好像某個小伙伴上廁所之前先把門關上(上鎖),完事之後再出來(解鎖),那麼其他小伙伴就可以再使用廁所了

例子:使用synchronized解決3.1售票問題

package li.thread.syn;

//使用多線程,模擬三個視窗同時售票共100張
public class SynSellTicket {
    public static void main(String[] args) {

        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1個線程-視窗
        new Thread(sellTicket03).start();//第2個線程-視窗
        new Thread(sellTicket03).start();//第3個線程-視窗
    }
}

//實現介面方式,使用synchronized實現線程同步
class SellTicket03 implements Runnable {

    private int ticketNum = 100;
    private boolean loop = true;//控制run方法變數

    public synchronized void sell() {//同步方法,在在同一時刻,只能有一個線程來執行run方法
        if (ticketNum <= 0) {
            System.out.println("售票結束...");
            loop = false;
            return;
        }
        //休眠50毫秒,模擬
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("視窗:" + Thread.currentThread().getName() + "售出一張票 "
                + "剩餘票數:" + (--ticketNum));
    }

    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一個同步方法
        }
    }
}
image-20220905135920187

8.1互斥鎖

  • 基本介紹
  1. Java語言中,引入了對象互斥鎖的額概念,來保證共用數據操作的完整性
  2. 每一個對象都對應於一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象
  3. 關鍵字synchronized來與對象的互斥鎖聯繫。當某個對象用synchronized修飾時,表明該對象在任一時刻只能有一個線程訪問
  4. 同步的局限性:導致程式的執行效率降低
  5. 非靜態的同步方法,鎖可以是this(當前對象),也可以是其他對象(要求鎖的是同一個對象)
  6. 同步方法(靜態的)的鎖為當前類本身(類.class)

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

  • 註意事項和細節:
    • 同步方法如果沒有使用static修飾:預設鎖對象為this
    • 如果方法使用static修飾,預設鎖對象:當前類.class
    • 實現的落地步驟:
      • 需要先分析上鎖的代碼
      • 選擇同步代碼塊或者同步方法
      • 要求多個線程的鎖對象為同一個
package li.thread.syn;

//使用多線程,模擬三個視窗同時售票共100張
public class SynSellTicket {
    public static void main(String[] args) {

        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1個線程-視窗
        new Thread(sellTicket03).start();//第2個線程-視窗
        new Thread(sellTicket03).start();//第3個線程-視窗
    }
}

//實現介面方式,使用synchronized實現線程同步
class SellTicket03 implements Runnable {

    private int ticketNum = 100;
    private boolean loop = true;//控制run方法變數
    Object object = new Object();

    //1.public synchronized static void m1(){}的鎖加在SellTicket03.class
    public synchronized static void m1(){}
    //2.如果在靜態方法中,要實現一個同步代碼塊則應該這樣寫:(原因是靜態方法適合類一起載入的,靜態方法不能使用this)
    public static void m2(){
        synchronized (SellTicket03.class){
            System.out.println("m2");
        }
    }

    // public synchronized void sell() {}就是一個同步方法。這時,鎖在this對象
    //也可以在代碼塊上寫synchronized,同步代碼塊,互斥鎖還是在this對象
    public /*synchronized*/void sell() {//同步方法
        synchronized (/*this*/object) {//如果是new Object就不是同一個對象
            if (ticketNum <= 0) {
                System.out.println("售票結束...");
                loop = false;
                return;
            }
            //休眠50毫秒,模擬
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("視窗:" + Thread.currentThread().getName() + "售出一張票 "
                    + "剩餘票數:" + (--ticketNum));
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一個同步方法
        }
    }
}

8.2線程的死鎖

  • 基本介紹:

多個線程都占用了對方的鎖資源,但不肯相讓,導致了死鎖。

在編程中一定要避免死鎖的發生。

例子:

package li.thread.syn;

//模擬線程死鎖
public class DeadLock_ {
    public static void main(String[] args) {
        //模擬死鎖現象
        DeadLockDemo A = new DeadLockDemo(true);
        DeadLockDemo B = new DeadLockDemo(false);
        A.setName("A線程");
        B.setName("B線程");
        A.start();
        B.start();
    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object();//保證多線程,共用一個對象,這裡使用static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {//構造器
        this.flag = flag;
    }

    @Override
    public void run() {
        //下麵業務邏輯的分析
        //1.如果flag為true,線程就會先得到/持有 o1對象鎖,然後嘗試去獲取o2對象鎖
        //2.如果線程A得不到o2對象鎖,就會Blocked
        //3.如果flag為false,線程B就會先得到/持有 o2對象鎖,然後嘗試去獲取o1對象鎖
        //4.如果線程B得不到o1對象鎖,就會Blocked
        if (flag) {
            synchronized (o1) {//對象互斥鎖,下麵就是同步代碼
                System.out.println(Thread.currentThread().getName() + "進入1");
                synchronized (o2) {//這裡獲得li對象的監視權
                    System.out.println(Thread.currentThread().getName() + "進入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + "進入3");
                synchronized (o1) {//這裡獲得li對象的監視權
                    System.out.println(Thread.currentThread().getName() + "進入4");
                }
            }
        }
    }
}

如下圖:兩個線程卡住了

image-20220905175553591

8.3釋放鎖

下麵操作會釋放鎖:

  1. 當前線程的同步方法、同步代碼塊執行結束
  2. 當前線程在同步代碼塊、同步方法中遇到break、return
  3. 當前線程在同步代碼塊、同步方法中出現了未處理的Error或者Exception,導致異常結束
  4. 當前線程在同步代碼塊、同步方法中執行了線程對象的wait()方法,當前線程暫停,並釋放鎖

下麵的操作不會釋放鎖:

  1. 線程執行同步代碼塊或同步方法時,程式調用Thread.sleep()、Thread.yield()方法暫停當前線程的執行,不會釋放鎖

  2. 線程執行同步代碼塊時,其他線程調用了該線程的suspend()方法將該線程掛起,該線程不會釋放鎖。

​ 提示:應儘量避免使用suspend()和resume()來控制線程,這兩個方法不再推薦使用

9.本章作業

9.1線程HomeWork01

(1)在main方法中啟動兩個線程

(2)第一個線程迴圈隨機列印100以內的整數

(3)直到第二個線程從鍵盤讀取了“Q”命令

image-20220905192152630

練習:

package li.thread.syn.homework;

import java.util.Scanner;

//(1)在main方法中啟動兩個線程
public class ThreadHomeWork01 {

    public static void main(String[] args) {

        A a = new A();
        B b = new B(a);//註意把a對象傳入b構造器中

        a.start();
        b.start();
    }
}

//創建A線程類
class A extends Thread {
    private boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            System.out.println((int) (Math.random() * 100 + 1));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("a線程退出...");
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

//創建B線程類
class B extends Thread {
    private A a;
    Scanner scanner = new Scanner(System.in);

    public B(A a) {
        this.a = a;
    }

    @Override
    public void run() {
        while (true) {
            //接到用戶輸入
            System.out.println("請輸入你的指令(Q)表示退出");
            char key = scanner.next().toUpperCase().charAt(0);
            if (key == 'Q') {
                //以通知的方式結束a線程
                a.setLoop(false);
                System.out.println("b線程退出...");
                break;
            }
        }
    }
}
image-20220905195149476

9.2線程線程HomeWork02

(1)有兩個用戶分別從同一張卡上取錢(總額10000)

(2)每次都取1000,當餘額不足時,就不能取款了

(3)不能出現超取現象==>線程同步問題

image-20220905201833154

易錯點:關於互斥鎖的理解
對於普通同步方法,鎖是當前實例對象
對於靜態同步方法,鎖是當前類的Class對象
對於同步方法塊,鎖是Synchonized括弧里配置的對象

package li.thread.syn.homework;

public class ThreadHomeWork02 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        Thread thread2 = new Thread(t);
        thread1.setName("t1");
        thread2.setName("t2");
        thread1.start();
        thread2.start();
    }
}

class T implements Runnable {
    private int money = 10000;

    @Override
    public void run() {
        while (true) {//while不要放到同步代碼塊裡面
            //1.使用了synchronized實現線程同步
            //2.當多個線程執行到這裡的時候就會去爭奪 this對象鎖
            //3.哪個線程爭奪到(獲取)this對象鎖,就執行synchronized代碼塊
            //4.爭奪不到this對象鎖,就Blocked,準備繼續爭奪
            //5.this對象鎖是非公平鎖
            synchronized (this) {
                if (money <= 0) {
                    System.out.println("餘額不足...");
                    break;
                }
                money -= 1000;
                System.out.println(Thread.currentThread().getName() + "取出了1000元" + " 當前餘額為:" + money);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
image-20220905205103018
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 定義:適配器模式是將一個類的介面轉換成客戶希望的另一個介面,適配器模式使得原本由於介面不相容而不能一起工作的類可以一起工作,在軟體設計中我們需要將一些“現存的對象”放到新的環境中,而新環境要求的介面是現對象所不能滿足的,我們可以使用這種模式進行介面適配轉換,使得“老對象”符合新環境的要求。 使用場景 ...
  • 構建者是一種可以將複雜對象的構建和表示分離開來,從而使得一個構建過程可以生成多個不同的表示對象。建造者模式通過一步一步構建對象。 ...
  • 觀察者模式又叫發佈-訂閱(Publish-Subscribe)模式,是對象的行為模式,訂閱是表示這些觀察者對象需要向目標對象進行註冊,這樣目標對象才知道有哪些對象在觀察它。發佈指的是當目標對象的狀態改變時,它就向它所有的觀察者對象發佈狀態更改的消息,以讓這些觀察者對象知曉。定義對象間的一種一對多的依... ...
  • 工作中總是遇到數據存儲相關的 Bug 工單,新需求開發設計中也多多少少會有數據模型設計和存儲相關的問題。經過幾次存儲方案設計選型和討論後發現需要有更全面的思考框架。 日常開發中常用的存儲方案選型很多都是 “拿來主義” 的,憑藉著經驗、習慣選用,但對它們的細節特性或約束少有研究。 除了手邊會用的存儲方... ...
  • 坦克大戰【3】 筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 坦克大戰0.6版 √增加功能 防止敵人坦克重疊運動 記錄玩家的成績(累積擊毀敵方坦克數),存檔退出【io流】 記錄當時的敵人坦克坐標與方向,存檔退出【io流】 玩游戲時 ...
  • 發現問題 前幾天在看別人的項目的時候,發現一個問題,簡單復現一下這個問題 // 註意這是一個Integer對象的數組哦 Integer[] arr = new Integer[]{9999,88,77}; List<Integer> list = Arrays.asList(arr); // 執行以 ...
  • 前置知識 什麼是進程,什麼又是線程?咱不是講系統,簡單說下,知道個大概就好了。 進程:一個可執行文件執行的過程。 線程:操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務 什 ...
  • Spring(三)——AOP 概念 什麼是AOP (1)面向切麵編程(方面),利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。 (2)通俗描述:不通過修改源代碼方式,在主幹功能裡面添加新功能 AOP底層原理 JDK動態 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...