<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java多線程編程(同步、死鎖、生產(chǎn)消費(fèi)者問(wèn)題)

          共 16936字,需瀏覽 34分鐘

           ·

          2021-04-25 10:03

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

            作者 |  xbhog

          來(lái)源 |  urlify.cn/Z3yMzu

          關(guān)于線程同步以及死鎖問(wèn)題:

          線程同步概念:是指若干個(gè)線程對(duì)象并行進(jìn)行資源的訪問(wèn)時(shí)實(shí)現(xiàn)的資源處理保護(hù)操作;

          線程死鎖概念:是指兩個(gè)線程都在等待對(duì)方先完成,造成程序的停止的狀態(tài);

          先了解相應(yīng)的概念,后面深入理解。

          同步:

          舉個(gè)例子:還是賣票問(wèn)題(經(jīng)典?)

          1. 不存在同步

          2. 開啟三個(gè)線程(售票員)測(cè)試

          package com.xbhog;
          class MyThread implements Runnable {// 定義線程執(zhí)行類
              private int ticket = 3;// 總票數(shù)為6張
              @Override
              public void run() {
                  while (true) { // 持續(xù)賣票
                      if (this.ticket > 0) { // 還有剩余票
                          try {
                              Thread.sleep(100); // 模擬網(wǎng)絡(luò)延遲
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          //獲取當(dāng)前線程的名字
                          System.out.println(Thread.currentThread().getName() +
                                  "賣票,ticket = " + this.ticket--);
                      } else {
                          System.out.println("***** 票已經(jīng)賣光了 *****");
                          break;// 跳出循環(huán)
                      }
                  }
              }
          }
          public class Java多線程核心 {
              public static void main(String[] args) throws Exception {
                  MyThread mt = new MyThread();
                  new Thread(mt, "售票員A").start(); // 開啟賣票線程
                  new Thread(mt, "售票員B").start(); // 開啟賣票線程
                  new Thread(mt, "售票員C").start(); // 開啟賣票線程
              }
          }

          結(jié)果:

          第一次隨機(jī)運(yùn)行:第二次隨機(jī)運(yùn)行:
          售票員B賣票,ticket = 2
          售票員C賣票,ticket = 3
          售票員A賣票,ticket = 3
          售票員A賣票,ticket = 1
          售票員B賣票,ticket = -1
          ***** 票已經(jīng)賣光了 
          售票員C賣票,ticket = 0
          票已經(jīng)賣光了 票已經(jīng)賣光了 *****
          售票員B賣票,ticket = 1
          ***** 票已經(jīng)賣光了 
          售票員A賣票,ticket = 3
          票已經(jīng)賣光了 
          售票員C賣票,ticket = 2
          票已經(jīng)賣光了 *****

          存在上述原因是因?yàn)樵诖a中兩個(gè)地方存在多線程訪問(wèn)時(shí)出現(xiàn)模糊的問(wèn)題:

          1. this.ticket>0;

          2. this,ticket--;

          假設(shè)現(xiàn)在剩余的票數(shù)為1張;當(dāng)?shù)谝粋€(gè)線程滿足售票的條件的時(shí)候(此時(shí)還未減少票數(shù)),其他的線程也可能同時(shí)滿足售票的條件,這樣同時(shí)進(jìn)行自減減就可能造成負(fù)數(shù)!

          解決上述問(wèn)題就需要采用線程同步技術(shù)實(shí)現(xiàn);

          首先需要明確,在Java中實(shí)現(xiàn)線程同步(synchronized)的方法有兩個(gè):

          1. 同步代碼塊(同步策略加在方法內(nèi)部)

            package com.xbhog.多線程1;
            class MyThread implements Runnable {      // 定義線程執(zhí)行類
                private int ticket = 3;         // 總票數(shù)為6張
                @Override
                public void run() {
                    while (true) {         // 持續(xù)賣票
                        synchronized(this) {       // 同步代碼塊
                            if (this.ticket > 0) {     // 還有剩余票
                                try {
                                    Thread.sleep(100);    // 模擬網(wǎng)絡(luò)延遲
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName() +
                                        "賣票,ticket = " + this.ticket--);
                            } else {
                                System.out.println("***** 票已經(jīng)賣光了 *****");
                                break;        // 跳出循環(huán)
                            }
                        }
                    }
                }
            }
            public class Java多線程同步代碼塊 {
                public static void main(String[] args) {
                    MyThread mt = new MyThread();
                    new Thread(mt, "售票員A").start();     // 開啟賣票線程
                    new Thread(mt, "售票員B").start();     // 開啟賣票線程
                    new Thread(mt, "售票員C").start();     // 開啟賣票線程
                }
            }


            售票員A賣票,ticket = 3
            售票員C賣票,ticket = 2
            售票員B賣票,ticket = 1
            ***** 票已經(jīng)賣光了 *****
            ***** 票已經(jīng)賣光了 *****
            ***** 票已經(jīng)賣光了 *****
          2. 同步方法(同步策略加在方法上)

            class MyThread implements Runnable {      // 定義線程執(zhí)行類
             private int ticket = 3;         // 總票數(shù)為6張
             @Override
             public void run() {
              while (this.sale()) {        // 調(diào)用同步方法
               ;
              }
             }
             public synchronized boolean sale() {     // 售票操作
              if (this.ticket > 0) {
               try {
                Thread.sleep(100);       // 模擬網(wǎng)絡(luò)延遲
               } catch (InterruptedException e) {
                e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName() + 
                "賣票,ticket = " + this.ticket--);
               return true;
              } else {
               System.out.println("***** 票已經(jīng)賣光了 *****");
               return false;
              }
             }
            }
            public class ThreadDemo {
             public static void main(String[] args) throws Exception {
              MyThread mt = new MyThread();
              new Thread(mt, "售票員A").start();     // 開啟賣票線程
              new Thread(mt, "售票員B").start();     // 開啟賣票線程
              new Thread(mt, "售票員C").start();     // 開啟賣票線程
             }
            }

            售票員A賣票,ticket = 3
            售票員C賣票,ticket = 2
            售票員B賣票,ticket = 1
            ***** 票已經(jīng)賣光了 *****
            ***** 票已經(jīng)賣光了 *****
            ***** 票已經(jīng)賣光了 *****


          同步的本質(zhì):在同一個(gè)時(shí)間段只允許有一個(gè)線程執(zhí)行資源,所以在此線程對(duì)象未執(zhí)行完的過(guò)程中其他線程對(duì)象將處于等待的狀態(tài)。

          同步的優(yōu)點(diǎn)與缺點(diǎn):

          1. 可以保證數(shù)據(jù)的準(zhǔn)確性

          2. 數(shù)據(jù)線程的訪問(wèn)安全


          3. 程序的處理性能下降

          死鎖:

          實(shí)例:

          假如現(xiàn)在又張三想要李四的畫,李四想要張三的書,那么張三對(duì)李四說(shuō):把你的畫給我,我就給你書;

          李四對(duì)張三說(shuō):把你的書給我,我就給你畫;

          此時(shí):張三在等待李四,李四在等待張三,兩人一直等待下去形成死鎖;

          觀察線程的死鎖:(實(shí)現(xiàn)張三李四)

          package com.xbhog.死鎖;

          class Book {
              public synchronized void tell(Painting paint) {  // 同步方法
                  System.out.println("張三對(duì)李四說(shuō):把你的畫給我,我就給你書,不給畫不給書!");
                  paint.get();
              }
              public synchronized void get() {      // 同步方法
                  System.out.println("張三得到了李四的畫開始認(rèn)真欣賞。");
              }
          }
          class Painting {
              public synchronized void tell(Book book) {    // 同步方法
                  System.out.println("李四對(duì)張三說(shuō):把你的書給我,我就給你畫,不給書不給畫!");
                  book.get();
              }
              public synchronized void get() {      // 同步方法
                  System.out.println("李四得到了張三的書開始認(rèn)真閱讀。");
              }
          }
          public class DeadLock implements Runnable{
              private Book book = new Book();
              private Painting paint = new Painting();
              public DeadLock() {
                  new Thread(this).start();
                  book.tell(paint);
              }
              @Override
              public void run() {
                  paint.tell(book);
              }
              public static void main(String[] args) {
                  new DeadLock() ;
              }
          }

          由于現(xiàn)在電腦的配置問(wèn)題,該代碼有可能在一次運(yùn)行中展示不出效果來(lái),需要多次運(yùn)行觀察效果;

          效果圖:

          由此引申出了生產(chǎn)者與消費(fèi)者模型。

          生產(chǎn)者與消費(fèi)者問(wèn)題:

          首先需要明確生產(chǎn)者與消費(fèi)者為兩個(gè)線程對(duì)象,是對(duì)同一資源進(jìn)行數(shù)據(jù)的保存與讀取;

          基本操作是:生產(chǎn)者生產(chǎn)一個(gè)資源,消費(fèi)者則取走一個(gè)資源,一一對(duì)應(yīng)。

          對(duì)應(yīng)類關(guān)系圖:

          我們需要設(shè)想一個(gè)問(wèn)題,如果不加任何操作的話,會(huì)出現(xiàn)什么問(wèn)題?

          1. 數(shù)據(jù)錯(cuò)位:當(dāng)生產(chǎn)者線程只是開辟了一個(gè)棧空間保存信息名稱,在想存數(shù)據(jù)但是還沒(méi)存數(shù)據(jù)的時(shí)候切換到了消費(fèi)者線程上,那么消費(fèi)者線程將會(huì)把這個(gè)信息名稱與上個(gè)信息的內(nèi)容進(jìn)行結(jié)合聯(lián)系,這樣就造成了數(shù)據(jù)的錯(cuò)位。

          2. 重復(fù)數(shù)據(jù):當(dāng)生產(chǎn)者放了若干次的數(shù)據(jù),消費(fèi)者才開始取數(shù)據(jù),或者消費(fèi)者取完,但生產(chǎn)者還沒(méi)生產(chǎn)新數(shù)據(jù)時(shí)又取了直接已經(jīng)取過(guò)得數(shù)據(jù)。

          解決以上兩個(gè)問(wèn)題需要涉及到以下兩個(gè)知識(shí)點(diǎn):

          1. 設(shè)置同步代碼塊或設(shè)置同步方法>>>解決數(shù)據(jù)錯(cuò)誤問(wèn)題

          2. Object線程等待與喚醒>>>解決數(shù)據(jù)重復(fù)設(shè)置以及重復(fù)取出的問(wèn)題

          增加數(shù)據(jù)同步方法或同步代碼塊:

          在本程序中,生產(chǎn)者與消費(fèi)者代表的都是線程對(duì)象,所以同步操作只能在Message類中,可以將set與get方法設(shè)置為單獨(dú)的同步方法。

          class Message {
           private String title ;       // 保存信息的標(biāo)題
           private String content ;       // 保存信息的內(nèi)容
           public synchronized void set(String title, String content) {
            this.title = title;
            try {
             Thread.sleep(200);
            } catch (InterruptedException e) {
             e.printStackTrace();
            }
            this.content = content;
           }
           public synchronized String get() {
            try {
             Thread.sleep(100);
            } catch (InterruptedException e) {
             e.printStackTrace();
            }
            return this.title + " --> " + this.content;
           }
           // setter、getter略
          }
          class Producer implements Runnable {     // 定義生產(chǎn)者
           private Message msg = null ;
           public Producer(Message msg) {
            this.msg = msg ;
           }
           @Override
           public void run() {
            for (int x = 0; x < 50; x++) {    // 生產(chǎn)50次數(shù)據(jù)
             if (x % 2 == 0) {
              this.msg.set("xbhog","22") ; // 設(shè)置屬性
             } else {
              this.msg.set("xbhog","www.cnblog.cn/xbhog") ; // 設(shè)置屬性
             }
            }
           }
          }
          class Consumer implements Runnable {     // 定義消費(fèi)者
           private Message msg = null ;
           public Consumer (Message msg) {
            this.msg = msg ;
           }
           @Override
           public void run() {
            for (int x = 0; x < 50; x++) {    // 取走50次數(shù)據(jù)
             System.out.println(this.msg.get());   // 取得屬性
            }
           }
          }
          public class ThreadDemo {
           public static void main(String[] args) throws Exception {
            Message msg = new Message() ;     // 定義Message對(duì)象,用于保存和取出數(shù)據(jù)
            new Thread(new Producer(msg)).start() ;  // 啟動(dòng)生產(chǎn)者線程
            new Thread(new Consumer(msg)).start() ;  // 取得消費(fèi)者線程
           }
          }

          Object線程等待與喚醒機(jī)制:

          線程的等待與喚醒只能依靠Object來(lái)完成,如果想要讓生產(chǎn)者與消費(fèi)者一個(gè)一個(gè)拿,一個(gè)一個(gè)取,那么需要加入標(biāo)志位來(lái)確定線程的當(dāng)前狀態(tài);

          由圖所示:

          當(dāng)生產(chǎn)者線程與消費(fèi)者線程進(jìn)入時(shí),判斷當(dāng)前的標(biāo)志位是否為true,

          1. true:表示生產(chǎn)者可以生產(chǎn)資源,但是消費(fèi)者不能取走資源

          2. false:表示生產(chǎn)者不能生產(chǎn)資源,但是消費(fèi)者需要取走資源

          class Message {
           private String title ;
           private String content ;
           private boolean flag = true;      // 表示生產(chǎn)或消費(fèi)的形式
           // flag = true:允許生產(chǎn),但是不允許消費(fèi)
           // flag = false:允許消費(fèi),不允許生產(chǎn)
           public synchronized void set(String title,String content) {
            if (this.flag == false) {      // 無(wú)法進(jìn)行生產(chǎn),等待被消費(fèi)
             try {
              super.wait();
             } catch (InterruptedException e) {
              e.printStackTrace();
             } 
            }
            this.title = title ;
            try {
             Thread.sleep(100);
            } catch (InterruptedException e) {
             e.printStackTrace();
            }
            this.content = content ;
            this.flag = false ;        // 已經(jīng)生產(chǎn)過(guò)了
            super.notify();         // 喚醒等待的線程
           }
           public synchronized String get() {
            if (this.flag == true) {      // 還未生產(chǎn),需要等待
             try {
              super.wait();
             } catch (InterruptedException e) {
              e.printStackTrace();
             }
            }
            try {
             Thread.sleep(10);
            } catch (InterruptedException e) {
             e.printStackTrace();
            }
            try {
             return this.title + "  -  " + this.content ;
            } finally {          // 不管如何都要執(zhí)行
             this.flag = true ;        // 繼續(xù)生產(chǎn)
             super.notify();         // 喚醒等待線程
            }
           }
          }

          在本程序中追加一個(gè)數(shù)據(jù)產(chǎn)生與消費(fèi)的控制邏輯成員屬性,通過(guò)此程序的值控制實(shí)現(xiàn)線程的等待與喚醒處理操作,從而解決線程重復(fù)操作的問(wèn)題。





          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長(zhǎng)按上方微信二維碼 2 秒


          感謝點(diǎn)贊支持下哈 

          瀏覽 56
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草黄色免费网站 | 国产视频福利论坛 | 一级黄色电影A片 | 操操逼逼 | 国产毛片久久久久久久 |