中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java多線程連續打印abc實現方法詳解

發布時間:2020-09-05 11:31:58 來源:腳本之家 閱讀:144 作者:平凡希 欄目:編程語言

一道編程題如下:

實例化三個線程,一個線程打印a,一個線程打印b,一個線程打印c,三個線程同時執行,要求打印出10個連著的abc。

題目分析:

通過題意我們可以得出,本題需要我們使用三個線程,三個線程分別會打印6次字符,關鍵是如何保證順序一定是abc...呢。所以此題需要同步機制來解決問題!

令打印字符A的線程為ThreadA,打印B的ThreadB,打印C的為ThreadC。問題為三線程間的同步喚醒操作,主要的目的就是使程序按ThreadA->ThreadB->ThreadC->ThreadA循環執行三個線程,因此本人整理出了三種方式來解決此問題。

一、通過兩個鎖(不推薦,可讀性和安全性比較差)

package com.demo.test;

/**
 * 基于兩個lock實現連續打印abcabc....
 * @author lixiaoxi
 *
 */
public class TwoLockPrinter implements Runnable {

  // 打印次數
  private static final int PRINT_COUNT = 10;
  // 前一個線程的打印鎖
  private final Object fontLock;
  // 本線程的打印鎖
  private final Object thisLock;
  // 打印字符
  private final char printChar;

  public TwoLockPrinter(Object fontLock, Object thisLock, char printChar) {
    this.fontLock = fontLock;
    this.thisLock = thisLock;
    this.printChar = printChar;
  }

  @Override
  public void run() {
    // 連續打印PRINT_COUNT次
    for (int i = 0; i < PRINT_COUNT; i++) {
      // 獲取前一個線程的打印鎖
      synchronized (fontLock) {
        // 獲取本線程的打印鎖
        synchronized (thisLock) {
          //打印字符
          System.out.print(printChar);
          // 通過本線程的打印鎖喚醒后面的線程 
          // notify和notifyall均可,因為同一時刻只有一個線程在等待
          thisLock.notify();
        }
        // 不是最后一次則通過fontLock等待被喚醒
        // 必須要加判斷,不然雖然能夠打印10次,但10次后就會直接死鎖
        if(i < PRINT_COUNT - 1){
          try {
            // 通過fontLock等待被喚醒
            fontLock.wait();
            
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        
      }  
    }  
  }

  public static void main(String[] args) throws InterruptedException {
    // 打印A線程的鎖
    Object lockA = new Object();
    // 打印B線程的鎖
    Object lockB = new Object();
    // 打印C線程的鎖
    Object lockC = new Object();
    
    // 打印a的線程
    Thread threadA = new Thread(new TwoLockPrinter(lockC, lockA, 'A'));
    // 打印b的線程
    Thread threadB = new Thread(new TwoLockPrinter(lockA, lockB, 'B'));
    // 打印c的線程
    Thread threadC = new Thread(new TwoLockPrinter(lockB, lockC, 'C'));

    // 依次開啟a b c線程
    threadA.start();
    Thread.sleep(100); // 確保按順序A、B、C執行
    threadB.start();
    Thread.sleep(100);
    threadC.start();
    Thread.sleep(100);
  }

}

打印結果:

ABCABCABCABCABCABCABCABCABCABC

分析:

此解法為了為了確定喚醒、等待的順序,每一個線程必須同時持有兩個對象鎖,才能繼續執行。一個對象鎖是fontLock,就是前一個線程所持有的對象鎖,還有一個就是自身對象鎖thisLock。主要的思想就是,為了控制執行的順序,必須要先持有fontLock鎖,也就是前一個線程要釋放掉前一個線程自身的對象鎖,當前線程再去申請自身對象鎖,兩者兼備時打印,之后首先調用thisLock.notify()釋放自身對象鎖,喚醒下一個等待線程,再調用fontLock.wait()釋放prev對象鎖,暫停當前線程,等待再次被喚醒后進入循環。運行上述代碼,可以發現三個線程循環打印ABC,共10次。程序運行的主要過程就是A線程最先運行,持有C,A對象鎖,后釋放A鎖,喚醒B。線程B等待A鎖,再申請B鎖,后打印B,再釋放B鎖,喚醒C,線程C等待B鎖,再申請C鎖,后打印C,再釋放C鎖,喚醒A。看起來似乎沒什么問題,但如果你仔細想一下,就會發現有問題,就是初始條件,三個線程按照A,B,C的順序來啟動,按照前面的思考,A喚醒B,B喚醒C,C再喚醒A。但是這種假設依賴于JVM中線程調度、執行的順序,所以需要手動控制他們三個的啟動順序,即Thread.Sleep(100)。

二、通過一個ReentrantLock和三個conditon實現(推薦,安全性,性能和可讀性較高)

package com.demo.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基于一個ReentrantLock和三個conditon實現連續打印abcabc...
 * @author lixiaoxi
 *
 */
public class RcSyncPrinter implements Runnable{

  // 打印次數
  private static final int PRINT_COUNT = 10;
  // 打印鎖
  private final ReentrantLock reentrantLock;
  // 本線程打印所需的condition
  private final Condition thisCondtion;
  // 下一個線程打印所需要的condition
  private final Condition nextCondtion;
  // 打印字符
  private final char printChar;

  public RcSyncPrinter(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition, 
      char printChar) {
    this.reentrantLock = reentrantLock;
    this.nextCondtion = nextCondition;
    this.thisCondtion = thisCondtion;
    this.printChar = printChar;
  }

  @Override
  public void run() {
    // 獲取打印鎖 進入臨界區
    reentrantLock.lock();
    try {
      // 連續打印PRINT_COUNT次
      for (int i = 0; i < PRINT_COUNT; i++) {
        //打印字符
        System.out.print(printChar);
        // 使用nextCondition喚醒下一個線程
        // 因為只有一個線程在等待,所以signal或者signalAll都可以
        nextCondtion.signal();
        // 不是最后一次則通過thisCondtion等待被喚醒
        // 必須要加判斷,不然雖然能夠打印10次,但10次后就會直接死鎖
        if (i < PRINT_COUNT - 1) {
          try {
            // 本線程讓出鎖并等待喚醒
            thisCondtion.await();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }

      }
    } finally {
      // 釋放打印鎖
      reentrantLock.unlock();
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    // 寫鎖
    ReentrantLock lock = new ReentrantLock();
    // 打印a線程的condition
    Condition conditionA = lock.newCondition();
    // 打印b線程的condition
    Condition conditionB = lock.newCondition();
    // 打印c線程的condition
    Condition conditionC = lock.newCondition();
    // 實例化A線程
    Thread printerA = new Thread(new RcSyncPrinter(lock, conditionA, conditionB, 'A'));
    // 實例化B線程
    Thread printerB = new Thread(new RcSyncPrinter(lock, conditionB, conditionC, 'B'));
    // 實例化C線程
    Thread printerC = new Thread(new RcSyncPrinter(lock, conditionC, conditionA, 'C'));
    // 依次開始A B C線程
    printerA.start();
    Thread.sleep(100);
    printerB.start();
    Thread.sleep(100);
    printerC.start();
  }
}

打印結果:

ABCABCABCABCABCABCABCABCABCABC

分析:

仔細想想本問題,既然同一時刻只能有一個線程打印字符,那我們為什么不使用一個同步鎖ReentrantLock?線程之間的喚醒操作可以通過Condition實現,且Condition可以有多個,每個condition.await阻塞只能通過該condition的signal/signalall來喚醒!這是synchronized關鍵字所達不到的,那我們就可以給每個打印線程一個自身的condition和下一個線程的condition,每次打印字符后,調用下一個線程的condition.signal來喚醒下一個線程,然后自身再通過自己的condition.await來釋放鎖并等待喚醒。

三、通過一個鎖和一個狀態變量來實現(推薦)

package com.demo.test;

/**
 * 基于一個鎖和一個狀態變量實現連續打印abcabc...
 * @author lixiaoxi
 *
 */
public class StateLockPrinter {
  //狀態變量
  private volatile int state=0;
  
  // 打印線程
  private class Printer implements Runnable {
    //打印次數
    private static final int PRINT_COUNT=10;
    //打印鎖
    private final Object printLock;
    //打印標志位 和state變量相關
    private final int printFlag;
    //后繼線程的線程的打印標志位,state變量相關
    private final int nextPrintFlag;
    //該線程的打印字符
    private final char printChar;
    public Printer(Object printLock, int printFlag,int nextPrintFlag, char printChar) {
      super();
      this.printLock = printLock;
      this.printFlag=printFlag;
      this.nextPrintFlag=nextPrintFlag;
      this.printChar = printChar;
    }

    @Override
    public void run() {
      //獲取打印鎖 進入臨界區
      synchronized (printLock) {
        //連續打印PRINT_COUNT次
        for(int i=0;i<PRINT_COUNT;i++){
          //循環檢驗標志位 每次都阻塞然后等待喚醒
          while (state!=printFlag) {
            try {
              printLock.wait();
            } catch (InterruptedException e) {
              return;
            }
          }
          //打印字符
          System.out.print(printChar);
          //設置狀態變量為下一個線程的標志位
          state=nextPrintFlag;
          //注意要notifyall,不然會死鎖,因為notify只通知一個,
          //但是同時等待的是兩個,如果喚醒的不是正確那個就會沒人喚醒,死鎖了
          printLock.notifyAll();
        }
      }
    }
    
  }

  public void test() throws InterruptedException{
    //鎖
    Object lock=new Object();
    //打印A的線程
    Thread threadA=new Thread(new Printer(lock, 0,1, 'A'));
    //打印B的線程
    Thread threadB=new Thread(new Printer(lock, 1,2, 'B'));
    //打印C的線程
    Thread threadC=new Thread(new Printer(lock, 2,0, 'C'));
    //一次啟動A B C線程
    threadA.start();
    Thread.sleep(1000);
    threadB.start();
    Thread.sleep(1000);
    threadC.start();
  }
  
  public static void main(String[] args) throws InterruptedException {
    
    StateLockPrinter print = new StateLockPrinter();
    print.test();
  }
  
}

打印結果:

ABCABCABCABCABCABCABCABCABCABC

分析:

狀態變量是一個volatile的整型變量,0代表打印a,1代表打印b,2代表打印c,三個線程都循環檢驗標志位,通過阻塞前和阻塞后兩次判斷可以確保當前打印的正確順序,隨后線程打印字符,然后設置下一個狀態字符,喚醒其它線程,然后重新進入循環。

補充題

三個Java多線程循環打印遞增的數字,每個線程打印5個數值,打印周期1-75,同樣的解法:

package com.demo.test;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 數字打印,三個線程同時打印數字,第一個線程打印12345,第二個線程打印678910 .........
 * @author lixiaoxi
 *
 */
public class NumberPrinter {

  //打印計數器
  private final AtomicInteger counter=new AtomicInteger(0);
  
  private class Printer implements Runnable{
    //總共需要打印TOTAL_PRINT_COUNT次
    private static final int TOTAL_PRINT_COUNT = 5;
    //每次打印PER_PRINT_COUNT次
    private static final int PER_PRINT_COUNT = 5;
    //打印鎖
    private final ReentrantLock reentrantLock;
    //前一個線程的condition
    private final Condition afterCondition;
    //本線程的condition
    private final Condition thisCondtion;
    
    public Printer(ReentrantLock reentrantLock, Condition thisCondtion,Condition afterCondition) {
      super();
      this.reentrantLock = reentrantLock;
      this.afterCondition = afterCondition;
      this.thisCondtion = thisCondtion;
    }

    @Override
    public void run() {
      //進入臨界區
      reentrantLock.lock();
      try {
        //循環打印TOTAL_PRINT_COUNT次
        for(int i=0;i<TOTAL_PRINT_COUNT;i++){
          //打印操作
          for(int j=0;j<PER_PRINT_COUNT;j++){
            //以原子方式將當前值加 1。
            //incrementAndGet返回的是新值(即加1后的值)
            System.out.println(counter.incrementAndGet());
          }
          //通過afterCondition通知后面線程
          afterCondition.signalAll();
          if(i < TOTAL_PRINT_COUNT - 1){
            try {
              //本線程釋放鎖并等待喚醒
              thisCondtion.await();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
        }
      } finally {
        reentrantLock.unlock();
      }
    }
  }
  
  public void test() throws InterruptedException {
    //打印鎖
    ReentrantLock reentrantLock=new ReentrantLock();
    //打印A線程的Condition
    Condition conditionA=reentrantLock.newCondition();
    //打印B線程的Condition
    Condition conditionB=reentrantLock.newCondition();
    //打印C線程的Condition
    Condition conditionC=reentrantLock.newCondition();

    //打印線程A
    Thread threadA=new Thread(new Printer(reentrantLock,conditionA, conditionB));
    //打印線程B
    Thread threadB=new Thread(new Printer(reentrantLock, conditionB, conditionC));
    //打印線程C
    Thread threadC=new Thread(new Printer(reentrantLock, conditionC, conditionA));
    // 依次開啟a b c線程
    threadA.start();
    Thread.sleep(100);
    threadB.start();
    Thread.sleep(100);
    threadC.start();
  }
  
  public static void main(String[] args) throws InterruptedException {
    NumberPrinter print = new NumberPrinter();
    print.test();
  }
}

運行結果:

2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

德清县| 青田县| 咸宁市| 周宁县| 潜山县| 永平县| 抚松县| 黔南| 瓮安县| 隆林| 邯郸市| 邮箱| 墨竹工卡县| 固始县| 临澧县| 沁水县| 体育| 武乡县| 宾川县| 同德县| 沙洋县| 泸州市| 牡丹江市| 河西区| 吴堡县| 安徽省| 遂昌县| 渝北区| 萨迦县| 自治县| 中西区| 靖边县| 奎屯市| 洛隆县| 平南县| 新疆| 南川市| 安徽省| 五台县| 普宁市| 高要市|