跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 三线程打印 ABC,循环十次的 N 种实现方式
未分類
7 5 月 2020

三线程打印 ABC,循环十次的 N 种实现方式

三线程打印 ABC,循环十次的 N 种实现方式

資深大佬 : mightofcode 62

编写一个程序,开启 3 个线程 A,B,C,这三个线程的输出分别为 A、B、C,每个线程将自己的 输出在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。如:ABCABCABC….

核心在于多线程同步

完整代码: https://github.com/mightofcode/javacc

方法 1,轮询 AtomicInteger

缺点是轮询白耗 CPU,性能很差

package com.mocyx.javacc.printer;  import org.springframework.stereotype.Component;  import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger;  /**  * 轮询 AtomicInteger 实现交替输出 ABC  * @author Administrator  */ @Component public class PollingPrinter implements Printer {      private final AtomicInteger atomicInteger = new AtomicInteger(0);      class Worker implements Runnable {          private String pstr;         private int index;         private int gap;         private int max;          public Worker(String pstr, int index, int gap, int max) {             this.pstr = pstr;             this.index = index;             this.gap = gap;             this.max = max;         }          @Override         public void run() {             while (true) {                 int v = atomicInteger.get();                 if (v == max) {                     return;                 } else {                     if (v % gap == index) {                         System.out.print(pstr);                         atomicInteger.set(v + 1);                     }                 }             }         }     }       @Override     public void print() {         List<Thread> threads = new ArrayList<>();         threads.add(new Thread(new Worker("A", 0, 3, 30)));         threads.add(new Thread(new Worker("B", 1, 3, 30)));         threads.add(new Thread(new Worker("C", 2, 3, 30)));         for (Thread t : threads) {             t.start();         }         try {             for (Thread t : threads) {                 t.join();             }         } catch (Exception e) {             e.printStackTrace();         }     } }  

方法 2,使用 Lock & Condition 同步

使用 Lock & Condition 要注意: 1 检查条件谓词,避免信号丢失和过早唤醒 2 注意在 finally 中进行 unlock,否则出现异常会 hang 住

package com.mocyx.javacc.printer;  import org.springframework.stereotype.Component;  import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;  /**  * 使用 Lock and Condition 进行同步  *  * @author Administrator  */ @Component public class SignalPrinter implements Printer {     private final Lock lock = new ReentrantLock();     private volatile int counter = 0;      class Worker implements Runnable {          Condition curCondition;         Condition nextCondition;         String pstr;         int max;         int index;         int gap;          public Worker(String pstr, int index, int gap, int max, Condition curCondition, Condition nextCondition) {             this.pstr = pstr;             this.max = max;             this.curCondition = curCondition;             this.nextCondition = nextCondition;             this.index = index;             this.gap = gap;         }          private boolean isMyTurn() {             return counter % gap == index;         }          @Override         public void run() {             while (true) {                 lock.lock();                 try {                     while (!isMyTurn()) {                         try {                             curCondition.await();                         } catch (InterruptedException e) {                             e.printStackTrace();                         }                     }                     if (counter < max) {                         System.out.print(pstr);                     }                     counter += 1;                     nextCondition.signalAll();                 } finally {                     lock.unlock();                 }                 if (counter >= max) {                     return;                 }             }         }     }      @Override     public void print() {         List<Thread> threads = new ArrayList<>();         List<Condition> conditions = new ArrayList<>();          conditions.add(lock.newCondition());         conditions.add(lock.newCondition());         conditions.add(lock.newCondition());          threads.add(new Thread(new Worker("A", 0, 3, 30, conditions.get(0), conditions.get(1))));         threads.add(new Thread(new Worker("B", 1, 3, 30, conditions.get(1), conditions.get(2))));         threads.add(new Thread(new Worker("C", 2, 3, 30, conditions.get(2), conditions.get(0))));          for (Thread t : threads) {             t.start();         }          try {             for (Thread t : threads) {                 t.join();             }         } catch (Exception e) {             e.printStackTrace();         }      } }  

方法 3,使用 Semphore 进行同步

相比 Lock & Condition,使用 Semphore 代码比较简洁,不容易出错

package com.mocyx.javacc.printer;  import org.springframework.stereotype.Component;  import java.util.ArrayList; import java.util.List; import java.util.concurrent.Semaphore;  /**  * @author Administrator  */ @Component public class SemaphorePrinter implements Printer {        class Worker implements Runnable {         private String pstr;          private Semaphore curSemphore;         private Semaphore nextSemphore;         private int count = 0;          Worker(String pstr, int count, Semaphore curSemphore, Semaphore nextSemphore) {             this.pstr = pstr;             this.count = count;             this.curSemphore = curSemphore;             this.nextSemphore = nextSemphore;         }          @Override         public void run() {             for (int i = 0; i < count; i++) {                 try {                     curSemphore.acquire(1);                     System.out.print(pstr);                     nextSemphore.release(1);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         }     }      @Override     public void print() {         List<Thread> threads = new ArrayList<>();         List<Semaphore> semaphores = new ArrayList<>();          semaphores.add(new Semaphore(0));         semaphores.add(new Semaphore(0));         semaphores.add(new Semaphore(0));          threads.add(new Thread(new Worker("A", 10, semaphores.get(0), semaphores.get(1))));         threads.add(new Thread(new Worker("B", 10, semaphores.get(1), semaphores.get(2))));         threads.add(new Thread(new Worker("C", 10, semaphores.get(2), semaphores.get(0))));          for (Thread t : threads) {             t.start();         }          semaphores.get(0).release(1);          try {             for (Thread t : threads) {                 t.join();             }         } catch (Exception e) {             e.printStackTrace();         }     } }  

方法 4,使用 BlockingQueue 进行同步

思路跟 go channel 类似,通过 BlockingQueue 传递信息

package com.mocyx.javacc.printer;   import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;  /**  * 使用阻塞队列进行同步  *  * @author Administrator  */ public class QueuePrinter implements Printer {       static class Msg {         public static final Msg PRINT_SUCCESS = new Msg();         public static final Msg PRINT = new Msg();         public static final Msg QUIT = new Msg();     }      class Channel {         BlockingQueue<Msg> inQueue = new ArrayBlockingQueue<Msg>(100);         BlockingQueue<Msg> outQueue = new ArrayBlockingQueue<Msg>(100);     }      class Worker implements Runnable {          Channel inChannel;         String pstr;          Worker(String pstr, Channel inChannel) {             this.inChannel = inChannel;             this.pstr = pstr;         }          @Override         public void run() {             while (true) {                 try {                     Msg msg = inChannel.inQueue.take();                     if (msg == Msg.PRINT) {                         System.out.print(pstr);                         inChannel.outQueue.put(Msg.PRINT_SUCCESS);                     } else if (msg == Msg.QUIT) {                         return;                     }                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         }     }      @Override     public void print() {         List<Thread> threads = new ArrayList<>();          List<Channel> channels = new ArrayList<>();         channels.add(new Channel());         channels.add(new Channel());         channels.add(new Channel());           threads.add(new Thread(new Worker("A", channels.get(0))));         threads.add(new Thread(new Worker("B", channels.get(1))));         threads.add(new Thread(new Worker("C", channels.get(2))));          for (Thread t : threads) {             t.start();         }          for (int i = 0; i < 30; i++) {             try {                 channels.get(i % channels.size()).inQueue.put(Msg.PRINT);                 channels.get(i % channels.size()).outQueue.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }         for (Channel c : channels) {             c.inQueue.add(Msg.QUIT);         }          try {             for (Thread t : threads) {                 t.join();             }         } catch (Exception e) {             e.printStackTrace();         }     }   }  

方法 0,Sleep 同步法

这个方法确实能工作,但是可能会被面试官打

package com.mocyx.javacc.printer;   import java.util.ArrayList; import java.util.List;  /**  * sleep and sleep  *  * @author Administrator  */ public class SleepPrinter implements Printer {      static class Worker implements Runnable {         private String printStr;         private int sleepGap;         private int delay;         private int count;          public Worker(String printStr,  int delay, int sleepGap, int count) {             this.printStr = printStr;             this.sleepGap = sleepGap;             this.delay = delay;             this.count = count;         }          @Override         public void run() {             try {                 Thread.sleep(delay);             } catch (InterruptedException e) {                 e.printStackTrace();             }             for (int i = 0; i < count; i++) {                 try {                     Thread.sleep(sleepGap);                     System.out.print(printStr);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         }     }        @Override     public void print() {         List<Thread> threads = new ArrayList<>();         threads.add(new Thread(new Worker("A", 00, 30, 10)));         threads.add(new Thread(new Worker("B", 10, 30, 10)));         threads.add(new Thread(new Worker("C", 20, 30, 10)));         for (Thread t : threads) {             t.start();         }         try {             for (Thread t : threads) {                 t.join();             }         } catch (Exception e) {             e.printStackTrace();         }      } }  

大佬有話說 (9)

  • 資深大佬 : lihongjie0209

    道理我都懂, 但是你在一个要求严格执行顺序的地方使用多线程的原因是什么? 有什么实际应用场景吗?

  • 資深大佬 : HuHui

    @lihongjie0209 面试就挺实际

  • 主 資深大佬 : mightofcode

    @lihongjie0209 工作中没机会用,拿面试题练练手

  • 資深大佬 : inwar

    应该把打印改成一个耗时任务,结果顺序输出,这样更实际一点

  • 資深大佬 : 1194129822

    现在所有 Java 类型的赋值操作是原子性的,所以 volatile 加 yield 不香吗?这样即使是轮询也查不到相当于阻塞了,CPU 消耗很少。

  • 主 資深大佬 : mightofcode

    @1194129822 为啥轮询查不到相当于阻塞了? yield 并不一定导致线程切换吧

  • 資深大佬 : luxinfl

    认真的说,我一个都不会

  • 資深大佬 : luxinfl

    while (true){
    lock.lock();
    if(in % 3 != index){
    lock.unlock();
    continue;
    }
    System.out.print(Thread.currentThread().getName());
    in++;
    if(in >31){
    lock.unlock();
    return;
    }
    lock.unlock();
    }

    这样写会不会很 ugly

  • 主 資深大佬 : mightofcode

    @luxinfl 还好,本质上跟方案 1 用 AtomicInteger 是一个策略:轮询

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具