27_多線程_第27天(線程安全、線程同步、等待喚醒機制、單例設計模式)_講義

来源:https://www.cnblogs.com/wanghui1234/archive/2018/09/02/9575392.html
-Advertisement-
Play Games

1、多線程安全問題 2、等待喚醒機制 ...


今日內容介紹
1、多線程安全問題
2、等待喚醒機制

01線程操作共用數據的安全問題

  *A:線程操作共用數據的安全問題
    如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。
    程式每次運行結果和單線程運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是線程安全的。

02售票的案例

*A:售票的案例

 /*
  * 多線程併發訪問同一個數據資源
  * 3個線程,對一個票資源,出售
  */
 public class ThreadDemo {
  public static void main(String[] args) {
    //創建Runnable介面實現類對象
    Tickets t = new Tickets();
    //創建3個Thread類對象,傳遞Runnable介面實現類
    Thread t0 = new Thread(t);
    Thread t1 = new Thread(t);
    Thread t2 = new Thread(t);
    
    t0.start();
    t1.start();
    t2.start();
    
  }
 }

 public class Tickets implements Runnable{
  
  //定義出售的票源
  private int ticket = 100;
  private Object obj = new Object();
  
  public void run(){
    while(true){
   
        if( ticket > 0){
          
          System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
        }
      
    }
  }
 }

03線程安全問題引發

*A:線程安全問題引發

/*
 * 多線程併發訪問同一個數據資源
 * 3個線程,對一個票資源,出售
 */
public class ThreadDemo {
 public static void main(String[] args) {
   //創建Runnable介面實現類對象
   Tickets t = new Tickets();
   //創建3個Thread類對象,傳遞Runnable介面實現類
   Thread t0 = new Thread(t);
   Thread t1 = new Thread(t);
   Thread t2 = new Thread(t);
   
   t0.start();
   t1.start();
   t2.start();
   
 }
}
/*
 *  通過線程休眠,出現安全問題
 */
public class Tickets implements Runnable{
 
 //定義出售的票源
 private int ticket = 100;
 private Object obj = new Object();
 
 public void run(){
   while(true){

     //對票數判斷,大於0,可以出售,變數--操作
       if( ticket > 0){
         try{
            Thread.sleep(10); //加了休眠讓其他線程有執行機會
         }catch(Exception ex){}
         System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
       }
   }
 }
}

04同步代碼塊解決線程安全問題

*A:同步代碼塊解決線程安全問題

  *A:售票的案例
      /*
       * 多線程併發訪問同一個數據資源
       * 3個線程,對一個票資源,出售
       */
      public class ThreadDemo {
       public static void main(String[] args) {
         //創建Runnable介面實現類對象
         Tickets t = new Tickets();
         //創建3個Thread類對象,傳遞Runnable介面實現類
         Thread t0 = new Thread(t);
         Thread t1 = new Thread(t);
         Thread t2 = new Thread(t);
         
         t0.start();
         t1.start();
         t2.start();
         
       }
      }
      /*
       *  通過線程休眠,出現安全問題
       *  解決安全問題,Java程式,提供技術,同步技術
       *  公式:
       *    synchronized(任意對象){
       *      線程要操作的共用數據
       *    }
       *    同步代碼塊
       */
      public class Tickets implements Runnable{
       
       //定義出售的票源
       private int ticket = 100;
       private Object obj = new Object();
       
       public void run(){
         while(true){
           //線程共用數據,保證安全,加入同步代碼塊
           synchronized(obj){
           //對票數判斷,大於0,可以出售,變數--操作
             if( ticket > 0){
               try{
                  Thread.sleep(10);
               }catch(Exception ex){}
               System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
             }
           }
         }
       }
      }

05同步代碼塊的執行原理

   A:同步代碼塊的執行原理
     同步代碼塊: 在代碼塊聲明上 加上synchronized
     synchronized (鎖對象) {
       可能會產生線程安全問題的代碼
     }
     同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。

06同步的上廁所原理

  *A:同步的上廁所原理
    a:不使用同步:線程在執行的過程中會被打擾
       線程比喻成人
       線程執行代碼就是上一個廁所
      第一個人正在上廁所,上到一半,被另外一個人拉出來
    b:使用同步:
       線程比喻成人
       線程執行代碼就是上一個廁所
       鎖比喻成廁所門
      第一個人上廁所,會鎖門
      第二個人上廁所,看到門鎖上了,等待第一個人上完再去上廁所

07同步方法

  *A:同步方法:
  /*
   * 多線程併發訪問同一個數據資源
   * 3個線程,對一個票資源,出售
   */
  public class ThreadDemo {
    public static void main(String[] args) {
      //創建Runnable介面實現類對象
      Tickets t = new Tickets();
      //創建3個Thread類對象,傳遞Runnable介面實現類
      Thread t0 = new Thread(t);
      Thread t1 = new Thread(t);
      Thread t2 = new Thread(t);

      t0.start();
      t1.start();
      t2.start();

    }
  }

  *A:同步方法
     /*
      *  採用同步方法形式,解決線程的安全問題
      *  好處: 代碼簡潔
      *  將線程共用數據,和同步,抽取到一個方法中
      *  在方法的聲明上,加入同步關鍵字
      *  
      *  問題:
      *    同步方法有鎖嗎,肯定有,同步方法中的對象鎖,是本類對象引用 this
      *    如果方法是靜態的呢,同步有鎖嗎,絕對不是this
      *    鎖是本類自己.class 屬性
      *    靜態方法,同步鎖,是本類類名.class屬性
      */
     public class Tickets implements Runnable{

      //定義出售的票源
      private  int ticket = 100;

      public void run(){
        while(true){
          payTicket();
        }
      }

      public  synchronized void payTicket(){  
          if( ticket > 0){
            try{
               Thread.sleep(10);
            }catch(Exception ex){}
            System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
          }

      }
     }

08JDK1.5新特性Lock介面

   *A:JDK1.5新特性Lock介面
        查閱API,查閱Lock介面描述,Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。
       Lock介面中的常用方法
            void lock()
            void unlock()
      Lock提供了一個更加面對對象的鎖,在該鎖中提供了更多的操作鎖的功能。
      我們使用Lock介面,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket

09Lock介面改進售票案例

   *A:Lock介面改進售票案例
      /*
       * 多線程併發訪問同一個數據資源
       * 3個線程,對一個票資源,出售
       */
      public class ThreadDemo {
        public static void main(String[] args) {
          //創建Runnable介面實現類對象
          Tickets t = new Tickets();
          //創建3個Thread類對象,傳遞Runnable介面實現類
          Thread t0 = new Thread(t);
          Thread t1 = new Thread(t);
          Thread t2 = new Thread(t);

          t0.start();
          t1.start();
          t2.start();

        }
      }
      /*
       *  使用JDK1.5 的介面Lock,替換同步代碼塊,實現線程的安全性
       *  Lock介面方法:
       *     lock() 獲取鎖
       *     unlock()釋放鎖
       *  實現類ReentrantLock
       */
      public class Tickets implements Runnable{

        //定義出售的票源
        private int ticket = 100;
        //在類的成員位置,創建Lock介面的實現類對象
        private Lock lock = new ReentrantLock();

        public void run(){
          while(true){
            //調用Lock介面方法lock獲取鎖
              lock.lock();
            //對票數判斷,大於0,可以出售,變數--操作
              if( ticket > 0){
                try{
                   Thread.sleep(10);
                   System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                }catch(Exception ex){

                }finally{
                  //釋放鎖,調用Lock介面方法unlock
                  lock.unlock();
                }
              }
          }
        }
      }

10線程的死鎖原理

   *A:線程的死鎖原理  
     當線程任務中出現了多個同步(多個鎖)  時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程式出現無限等待,
     這種現象我們稱為死鎖。這種情況能避免就 避免掉。
        synchronzied(A鎖){
            synchronized(B鎖){

            }
        }

11線程的死鎖代碼實現

   *A:線程的死鎖代碼實現
       public class DeadLock implements Runnable{
        private int i = 0;
        public void run(){
          while(true){
            if(i%2==0){
              //先進入A同步,再進入B同步
              synchronized(LockA.locka){
                System.out.println("if...locka");
                synchronized(LockB.lockb){
                  System.out.println("if...lockb");
                }
              }
            }else{
              //先進入B同步,再進入A同步
              synchronized(LockB.lockb){
                System.out.println("else...lockb");
                synchronized(LockA.locka){
                  System.out.println("else...locka");
                }
              }
            }
            i++;
          }
        }
       }

  public class DeadLockDemo {
    public static void main(String[] args) {
      DeadLock dead = new DeadLock();
      Thread t0 = new Thread(dead);
      Thread t1 = new Thread(dead);
      t0.start();
      t1.start();
    }
  }


  public class LockA {
    private LockA(){}
    
    public  static final LockA locka = new LockA();
  }


  public class LockB {
    private LockB(){}
    
    public static final LockB lockb = new LockB();
  }

### 12線程等待與喚醒案例介紹

   *A:線程等待與喚醒案例介紹 
     等待喚醒機制所涉及到的方法:
         wait() :等待,將正在執行的線程釋放其執行資格 和 執行權,並存儲到線程池中。
         notify():喚醒,喚醒線程池中被wait()的線程,一次喚醒一個,而且是任意的。
         notifyAll(): 喚醒全部:可以將線程池中的所有wait() 線程都喚醒。
       其實,所謂喚醒的意思就是讓 線程池中的線程具備執行資格。必須註意的是,這些方法都是在 同步中才有效。同時這些方法在使用
       時必須標明所屬鎖,這樣才可以明確出這些方法操作的到底是哪個鎖上的線程。

13線程等待與喚醒案例資源類編寫

  *A:線程等待與喚醒案例資源類編寫
    /*
     *  定義資源類,有2個成員變數
     *  name,sex
     *  同時有2個線程,對資源中的變數操作
     *  1個對name,age賦值
     *  2個對name,age做變數的輸出列印
     */
    public class Resource {
      public String name;
      public String sex;
    }

14線程等待與喚醒案例輸入和輸出線程

   A:線程等待與喚醒案例輸入和輸出線程
     /*
       *  輸入的線程,對資源對象Resource中成員變數賦值
       *  一次賦值 張三,男
       *  下一次賦值 lisi,nv
     */
      public class Input implements Runnable {
        private Resource r=new Resource();

        public void run() {
          int i=0;
          while(true){
            if(i%2==0){
               r.name="張三";
               r.sex="男";
             }else{
                r.name="lisi";
                r.sex="女";
              }
            i++;
          }
        }
      }

      /*
       *  輸出線程,對資源對象Resource中成員變數,輸出值
       */
      public class Output implements Runnable {
        private Resource r=new Resource() ;

        public void run() {
          while(true){
             System.out.println(r.name+"..."+r.sex); 
            }
          }
      }

15線程等待與喚醒案例測試類

   A:線程等待與喚醒案例測試類
      /*
       *  開啟輸入線程和輸出線程,實現賦值和列印值
       */
      public class ThreadDemo{
        public static void main(String[] args) {

          Resource r = new Resource();

          Input in = new Input();
          Output out = new Output();

          Thread tin = new Thread(in);
          Thread tout = new Thread(out);

          tin.start();
          tout.start();
        }
      }

16線程等待與喚醒案例null值解決

   A:線程等待與喚醒案例null值解決
        /*
        *  輸入的線程,對資源對象Resource中成員變數賦值
        *  一次賦值 張三,男
        *  下一次賦值 lisi,nv
      */
       public class Input implements Runnable {
         private Resource r;
         public Input(Resource r){
           this.r=r;
         }

         public void run() {
           int i=0;
           while(true){
             if(i%2==0){
                r.name="張三";
                r.sex="男";
              }else{
                 r.name="lisi"
                 r.sex="女"
               }
             i++;
           }
         }
       }

       /*
        *  輸出線程,對資源對象Resource中成員變數,輸出值
        */ 
       public class Output implements Runnable {
         private Resource r;
         public Output(Resource r){
            this.r=r;
         } 
         public void run() {
           while(true){
              System.out.println(r.name+"..."+r.sex); 
             }
           }
         }

       }
       /*
        *  開啟輸入線程和輸出線程,實現賦值和列印值
        */
       public class ThreadDemo{
         public static void main(String[] args) {

           Resource r = new Resource();

           Input in = new Input(r);
           Output out = new Output(r);

           Thread tin = new Thread(in);
           Thread tout = new Thread(out);

           tin.start();
           tout.start();
         }
       }

17線程等待與喚醒案例數據安全解決

A:線程等待與喚醒案例數據安全解決
        /*
          *  輸入的線程,對資源對象Resource中成員變數賦值
          *  一次賦值 張三,男
          *  下一次賦值 lisi,nv
        */
         public class Input implements Runnable {
           private Resource r;
           public Input(Resource r){
             this.r=r;
           }
          
           public void run() {
             int i=0;
             while(true){
              synchronized(r){
               if(i%2==0){
                  r.name="張三";
                  r.sex="男";
                }else{
                   r.name="lisi"
                   r.sex="女"
                 }
               i++;
             }

           }
         }

         /*
          *  輸出線程,對資源對象Resource中成員變數,輸出值
          */ 
         public class Output implements Runnable {
           private Resource r;
           public Output(Resource r){
              this.r=r;
           } 
           public void run() {
             while(true){
                synchronized(r){
                 System.out.println(r.name+"..."+r.sex); 
                }
               }
             }
           }

         }
         /*
          *  開啟輸入線程和輸出線程,實現賦值和列印值
          */
         public class ThreadDemo{
           public static void main(String[] args) {
             
             Resource r = new Resource();
             
             Input in = new Input(r);
             Output out = new Output(r);
             
             Thread tin = new Thread(in);
             Thread tout = new Thread(out);
             
             tin.start();
             tout.start();
           }
         }

18線程等待與喚醒案例通信的分析

*A:線程等待與喚醒案例通信的分析
    輸入:賦值後,執行方法wait()永遠等待
    輸出:變數值列印輸出,在輸出等待之前,喚醒
    輸入的notify(),自己在wait()永遠等待
    輸入:被喚醒後,重新對變數賦值,賦值後,必須喚醒輸出的線程notify(),
         自己的wait()

19線程等待與喚醒案例的實現

*A 線程等待與喚醒案例的實現

 /*
  *  定義資源類,有2個成員變數
  *  name,sex
  *  同時有2個線程,對資源中的變數操作
  *  1個對name,age賦值
  *  2個對name,age做變數的輸出列印
  */
 public class Resource {
  public String name;
  public String sex;
  public boolean flag = false;
 }

 /*
  *  輸入的線程,對資源對象Resource中成員變數賦值
  *  一次賦值 張三,男
  *  下一次賦值 lisi,nv
  */
 public class Input implements Runnable {
  private Resource r ;
  
  public Input(Resource r){
    this.r = r;
  }
  
  public void run() {
    int i = 0 ;
    while(true){
      synchronized(r){
        //標記是true,等待
          if(r.flag){
            try{r.wait();}catch(Exception ex){}
          }
        
        if(i%2==0){
          r.name = "張三";
          r.sex = "男";
        }else{
          r.name = "lisi";
          r.sex = "nv";
        }
        //將對方線程喚醒,標記改為true
        r.flag = true;
        r.notify();
      }
      i++;
    }
  }

 }
 
 /*
  *  輸出線程,對資源對象Resource中成員變數,輸出值
  */
 public class Output implements Runnable {
  private Resource r ;
  
  public Output(Resource r){
    this.r = r;
  }
  public void run() {
    while(true){
      synchronized(r){  
        //判斷標記,是false,等待
      if(!r.flag){
        try{r.wait();}catch(Exception ex){}
        }
      System.out.println(r.name+".."+r.sex);
      //標記改成false,喚醒對方線程
      r.flag = false;
      r.notify();
      }
    }
  }

 }

 /*
  *  開啟輸入線程和輸出線程,實現賦值和列印值
  */
 public class ThreadDemo{
  public static void main(String[] args) {
    
    Resource r = new Resource();
    
    Input in = new Input(r);
    Output out = new Output(r);
    
    Thread tin = new Thread(in);
    Thread tout = new Thread(out);
    
    tin.start();
    tout.start();
  }
 }

作業測試

1、wait和sleep的區別
2、線程的生命周期(五中狀態的切換流程)
3、有一個抽獎池,該抽獎池中存放了獎勵的金額,該抽獎池用一個數組int[] arr = {10,5,20,50,100,200,500,800,2,80,300};
創建兩個抽獎箱(線程)設置線程名稱分別為“抽獎箱1”,“抽獎箱2”,隨機從arr數組中獲取獎項元素並列印在控制臺上,格式如下:

抽獎箱1 又產生了一個 10 元大獎
抽獎箱2 又產生了一個 100 元大獎 
//.....

4、某公司組織年會,會議入場時有兩個入口,在入場時每位員工都能獲取一張雙色球彩票,假設公司有100個員工,利用多線程模擬年會入場過程,

並分別統計每個入口入場的人數,以及每個員工拿到的彩票的號碼。線程運行後列印格式如下:
編號為: 2 的員工 從後門 入場! 拿到的雙色球彩票號碼是: [17, 24, 29, 30, 31, 32, 07]
編號為: 1 的員工 從後門 入場! 拿到的雙色球彩票號碼是: [06, 11, 14, 22, 29, 32, 15]
//.....
從後門入場的員工總共: 13 位員工
從前門入場的員工總共: 87 位員工

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [TOC] 翻譯自《Demo Week: Tidy Time Series Analysis with tibbletime》 原文鏈接:www.business science.io/code tools/2017/10/26/demo_week_tibbletime.html 註意:由於軟體包的 ...
  • Process類參數介紹 group 參數未使用, 值始終為None target 表示調用對象, 即子進程要執行的任務 args 表示調用對象的位置參數元組, args=(1,2,'hades',) 是一個元組形式,必須有逗號 kwargs 表示調用對象的字典, kwargs={'name':'h ...
  • Maximum Multiple Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3241 Accepted Submission(s): 134 ...
  • 1.Spring Boot簡介 wiki上的介紹: Spring Boot是Spring的常規配置解決方案,用於創建可以“運行”的獨立的,生產級的基於Spring的應用程式。[22]它預先配置了Spring對Spring平臺和第三方庫的最佳配置和使用的“見解視圖”,因此您可以儘量少開始。大多數Spr ...
  • 一 、前言 本文設計思想採用明德揚至簡設計法。VGA是最常見的視頻顯示介面,時序也較為簡單。本文從利用顯示屏通過VGA方式顯示測試圖案及靜態圖片著手帶大家接觸圖像顯示應用,算是為後續VGA顯示攝像頭採集圖像以及HDMI高清數字顯示方式打個基礎。 二、VGA顯示原理 關於VGA的詳細解釋可查看參考文獻 ...
  • 類載入機制中的雙親委派模型是非常重要的,本文從源碼的角度對雙親委派模式進行瞭解析,源碼調用基本邏輯很簡單. ...
  • 1.MyBatis架構(簡單介紹MyBatis的流程) 接下來簡單介紹一下這張圖:首先明確我們的目的就是要創建sqlsession然後利用這個對象去執行sql 完成CRUD。創建sqlsession的前提就是用session工廠去創建,利用工廠創建需要原材料啊,所以最頂端的MyBatis配置文件就是 ...
  • 1、非同步消息 當一個消息發送時候,消息會被交給消息代理,消息代理可以確保消息被髮送到指定的目的地,同時解放發送者,使其能夠繼續進行其它業務。消息代理通常有ActiveMQ、RabbitMQ...,目的地通常有隊列和主題,隊列採用點對點的模型,主題採用發佈訂閱模型 點對點模型:消息隊列可以有多個接受者 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...