一道编程题如下:

实例化三个线程,一个线程打印a,一个打印b,一个打印c,三个线程同时执行,要求打印出6个连着的abc

题目分析:

通过题意我们可以得出,本题需要我们使用三个线程,三个线程分别会打印6次字符,关键是如何保证顺序一定是abc...呢。所以此题需要同步机制来解决问题!

令打印字符A的线程为ThreadA,打印B的ThreadB,打印C的为ThreadC。问题为三线程间的同步唤醒操作,主要的目的就是使程序按ThreadA->ThreadB->ThreadC-

>ThreadA循环执行三个线程,因此本人整理出了三种方式来解决此问题。

一.通过两个锁(不推荐,可读性和安全性比较差)

  1. /**
  2. * 基于两个lock实现连续打印abcabc....
  3. * @author fhr
  4. * @since 2017/09/04
  5. */
  6. public class TwoLockPrinter {
  7.  
  8. @Test
  9. public void test() throws InterruptedException {
  10. // 打印A线程的锁
  11. Object lockA = new Object();
  12. // 打印B线程的锁
  13. Object lockB = new Object();
  14. // 打印C线程的锁
  15. Object lockC = new Object();
  16. ThreadGroup group = new ThreadGroup("xx");
  17. // 打印a的线程
  18. Thread threadA = new Thread(group, new Printer(lockC, lockA, 'A'));
  19. // 打印b的线程
  20. Thread threadB = new Thread(group, new Printer(lockA, lockB, 'B'));
  21. // 打印c的线程
  22. Thread threadC = new Thread(group, new Printer(lockB, lockC, 'C'));
  23. // 依次开启a b c线程
  24. threadA.start();
  25. Thread.sleep(100);
  26. threadB.start();
  27. Thread.sleep(100);
  28. threadC.start();
  29. // 主线程循环让出cpu使用权
  30. while (group.activeCount() > 0) {
  31. Thread.yield();
  32. }
  33. }
  34.  
  35. // 打印线程
  36. private class Printer implements Runnable {
  37. // 打印次数
  38. private static final int PRINT_COUNT = 6;
  39. // 前一个线程的打印锁
  40. private final Object fontLock;
  41. // 本线程的打印锁
  42. private final Object thisLock;
  43. // 打印字符
  44. private final char printChar;
  45.  
  46. public Printer(Object fontLock, Object thisLock, char printChar) {
  47. super();
  48. this.fontLock = fontLock;
  49. this.thisLock = thisLock;
  50. this.printChar = printChar;
  51. }
  52. @Override
  53. public void run() {
  54. // 连续打印PRINT_COUNT次
  55. for (int i = 0; i < PRINT_COUNT; i++) {
  56. // 获取前一个线程的打印锁
  57. synchronized (fontLock) {
  58. // 获取本线程的打印锁
  59. synchronized (thisLock) {
  60. // 打印字符
  61. System.out.print(printChar);
  62. // 通过本线程的打印锁唤醒后面的线程
  63. // notify和notifyall均可,因为同一时刻只有一个线程在等待
  64. thisLock.notify();
  65. // 不是最后一次则通过fontLock等待被唤醒
  66. // 必须要加判断,不然能够打印6次 但6次后就会直接死锁
  67. if (i < PRINT_COUNT - 1) {
  68. try {
  69. // 通过fontLock等待被唤醒
  70. fontLock.wait();
  71. } catch (InterruptedException e) {
  72. e.printStackTrace();
  73. }
  74. }
  75. }
  76. }
  77. }
  78. }
  79. }
  80. }

此解法为了为了确定唤醒、等待的顺序,每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是fontLock,就是前一个线程所持有的对象锁,还有一个就是自身

对象锁thisLock。主要的思想就是,为了控制执行的顺序,必须要先持有fontLock锁,也就是前一个线程要释放掉前一个线程自身的对象锁,当前线程再去申请自身对象锁,两

者兼备时打印,之后首先调用thisLock.notify()释放自身对象锁,唤醒下一个等待线程,再调用fontLock.wait()释放prev对象锁,暂停当前线程,等待再次被唤醒后进入循环。运

行上述代码,可以发现三个线程循环打印ABC,共6次。程序运行的主要过程就是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实现(推荐,安全性,性能和可读性较高)

  1. /**
  2. * 基于一个ReentrantLock和三个conditon实现连续打印abcabc...
  3. * @author fhr
  4. * @since 2017/09/04
  5. */
  6. public class RcSyncPrinter {
  7.  
  8. @Test
  9. public void test() throws InterruptedException {
  10. ThreadGroup group = new ThreadGroup("xx");
  11. // 写锁
  12. ReentrantLock lock = new ReentrantLock();
  13. // 打印a线程的condition
  14. Condition conditionA = lock.newCondition();
  15. // 打印b线程的condition
  16. Condition conditionB = lock.newCondition();
  17. // 打印c线程的condition
  18. Condition conditionC = lock.newCondition();
  19. // 实例化A线程
  20. Thread printerA = new Thread(group, new Printer(lock, conditionA, conditionB, 'A'));
  21. // 实例化B线程
  22. Thread printerB = new Thread(group, new Printer(lock, conditionB, conditionC, 'B'));
  23. // 实例化C线程
  24. Thread printerC = new Thread(group, new Printer(lock, conditionC, conditionA, 'C'));
  25. // 依次开始A B C线程
  26. printerA.start();
  27. Thread.sleep(100);
  28. printerB.start();
  29. Thread.sleep(100);
  30. printerC.start();
  31. // 主线程循环让出CPU使用权
  32. while (group.activeCount() > 0) {
  33. Thread.yield();
  34. }
  35. }
  36.  
  37. // 打印线程
  38. private class Printer implements Runnable {
  39. // 打印次数
  40. private static final int PRINT_COUNT = 6;
  41. // 打印锁
  42. private final ReentrantLock reentrantLock;
  43. // 本线程打印所需的condition
  44. private final Condition thisCondtion;
  45. // 下一个线程打印所需要的condition
  46. private final Condition nextCondtion;
  47. // 打印字符
  48. private final char printChar;
  49.  
  50. public Printer(ReentrantLock reentrantLock, Condition thisCondtion, Condition nextCondition, char printChar) {
  51. this.reentrantLock = reentrantLock;
  52. this.nextCondtion = nextCondition;
  53. this.thisCondtion = thisCondtion;
  54. this.printChar = printChar;
  55. }
  56.  
  57. @Override
  58. public void run() {
  59. // 获取打印锁 进入临界区
  60. reentrantLock.lock();
  61. try {
  62. // 连续打印PRINT_COUNT次
  63. for (int i = 0; i < PRINT_COUNT; i++) {
  64. System.out.print(printChar);
  65. // 使用nextCondition唤醒下一个线程
  66. // 因为只有一个线程在等待,所以signal或者signalAll都可以
  67. nextCondtion.signal();
  68. // 不是最后一次则通过thisCondtion等待被唤醒
  69. // 必须要加判断,不然能够打印6次 但6次后就会直接死锁
  70. if (i < PRINT_COUNT - 1) {
  71. try {
  72. // 本线程让出锁并等待唤醒
  73. thisCondtion.await();
  74. } catch (InterruptedException e) {
  75. e.printStackTrace();
  76. }
  77. }
  78.  
  79. }
  80. } finally {
  81. // 释放打印锁
  82. reentrantLock.unlock();
  83. }
  84. }
  85. }
  86. }

仔细想想本问题,既然同一时刻只能有一个线程打印字符,那我们为什么不使用一个同步锁ReentrantLock?线程之间的唤醒操作可以通过Condition实现,且Condition可以有

多个,每个condition.await阻塞只能通过该condition的signal/signalall来唤醒!这是synchronized关键字所达不到的,那我们就可以给每个打印线程一个自身的condition和下一

个线程的condition,每次打印字符后,调用下一个线程的condition.signal来唤醒下一个线程,然后自身再通过自己的condition.await来释放锁并等待唤醒。

三.通过一个锁和一个状态变量来实现(推荐)

  1. /**
  2. * 基于一个锁和一个状态变量实现连续打印abcabc...
  3. * @author fhr
  4. * @since 2017/09/04
  5. */
  6. public class StateLockPrinter {
  7. //状态变量
  8. private volatile int state=0;
  9. @Test
  10. public void test() throws InterruptedException {
  11. //锁
  12. Object lock=new Object();
  13. ThreadGroup group=new ThreadGroup("xx");
  14. //打印A的线程
  15. Thread threadA=new Thread(group,new Printer(lock, 0,1, 'A'));
  16. //打印B的线程
  17. Thread threadB=new Thread(group,new Printer(lock, 1,2, 'B'));
  18. //打印C的线程
  19. Thread threadC=new Thread(group,new Printer(lock, 2,0, 'C'));
  20. //一次启动A B C线程
  21. threadA.start();
  22. Thread.sleep(1000);
  23. threadB.start();
  24. Thread.sleep(1000);
  25. threadC.start();
  26. //循环检查线程组合的活的线程数量
  27. while (group.activeCount()>0) {
  28. //让出CPU使用权
  29. Thread.yield();
  30. }
  31. }
  32. //打印线程
  33. private class Printer implements Runnable{
  34. //打印次数
  35. private static final int PRINT_COUNT=6;
  36. //打印锁
  37. private final Object printLock;
  38. //打印标志位 和state变量相关
  39. private final int printFlag;
  40. //后继线程的线程的打印标志位,state变量相关
  41. private final int nextPrintFlag;
  42. //该线程的打印字符
  43. private final char printChar;
  44. public Printer(Object printLock, int printFlag,int nextPrintFlag, char printChar) {
  45. super();
  46. this.printLock = printLock;
  47. this.printFlag=printFlag;
  48. this.nextPrintFlag=nextPrintFlag;
  49. this.printChar = printChar;
  50. }
  51.  
  52. @Override
  53. public void run() {
  54. //获取打印锁 进入临界区
  55. synchronized (printLock) {
  56. //连续打印PRINT_COUNT次
  57. for(int i=0;i<PRINT_COUNT;i++){
  58. //循环检验标志位 每次都阻塞然后等待唤醒
  59. while (state!=printFlag) {
  60. try {
  61. printLock.wait();
  62. } catch (InterruptedException e) {
  63. return;
  64. }
  65. }
  66. //打印字符
  67. System.out.print(printChar);
  68. //设置状态变量为下一个线程的标志位
  69. state=nextPrintFlag;
  70. //注意要notifyall,不然会死锁,因为notify只通知一个,
  71. //但是同时等待的是两个,如果唤醒的不是正确那个就会没人唤醒,死锁了
  72. printLock.notifyAll();
  73. }
  74. }
  75. }
  76. }
  77. }

状态变量是一个volatile的整型变量,0代表打印a,1代表打印b,2代表打印c,三个线程都循环检验标志位,通过阻塞前和阻塞后两次判断可以确保当前打印的正确顺序,随后线程

打印字符,然后设置下一个状态字符,唤醒其它线程,然后重新进入循环。

补充题

三个Java多线程循环打印递增的数字,每个线程打印5个数值,打印周期1-75,同样的解法:

  1. /**
  2. * 数字打印,三个线程同时打印数字,
  3. * 第一个线程打印12345,第二个线程打印678910 .........
  4. * @author fhr
  5. * @since 2017/09/04
  6. */
  7. public class NumberPrinter {
  8. //打印计数器
  9. private final AtomicInteger counter=new AtomicInteger(0);
  10.  
  11. @Test
  12. public void test() throws InterruptedException {
  13. //打印锁
  14. ReentrantLock reentrantLock=new ReentrantLock();
  15. //打印A线程的Condition
  16. Condition conditionA=reentrantLock.newCondition();
  17. //打印B线程的Condition
  18. Condition conditionB=reentrantLock.newCondition();
  19. //打印C线程的Condition
  20. Condition conditionC=reentrantLock.newCondition();
  21. ThreadGroup group=new ThreadGroup("xx");
  22. //打印线程A
  23. Thread threadA=new Thread(group,new Printer(reentrantLock,conditionA, conditionB));
  24. //打印线程B
  25. Thread threadB=new Thread(group,new Printer(reentrantLock, conditionB, conditionC));
  26. //打印线程C
  27. Thread threadC=new Thread(group,new Printer(reentrantLock, conditionC, conditionA));
  28. // 依次开启a b c线程
  29. threadA.start();
  30. Thread.sleep(100);
  31. threadB.start();
  32. Thread.sleep(100);
  33. threadC.start();
  34. while (group.activeCount()>0) {
  35. Thread.yield();
  36. }
  37. }
  38.  
  39. private class Printer implements Runnable{
  40. //总共需要打印TOTAL_PRINT_COUNT次
  41. private static final int TOTAL_PRINT_COUNT=5;
  42. //每次打印PER_PRINT_COUNT次
  43. private static final int PER_PRINT_COUNT=5;
  44. //打印锁
  45. private final ReentrantLock reentrantLock;
  46. //前一个线程的condition
  47. private final Condition afterCondition;
  48. //本线程的condition
  49. private final Condition thisCondtion;
  50.  
  51. public Printer(ReentrantLock reentrantLock, Condition thisCondtion,Condition afterCondition) {
  52. super();
  53. this.reentrantLock = reentrantLock;
  54. this.afterCondition = afterCondition;
  55. this.thisCondtion = thisCondtion;
  56. }
  57.  
  58. @Override
  59. public void run() {
  60. //进入临界区
  61. reentrantLock.lock();
  62. try {
  63. //循环打印TOTAL_PRINT_COUNT次
  64. for(int i=0;i<TOTAL_PRINT_COUNT;i++){
  65. //打印操作
  66. for(int j=0;j<PER_PRINT_COUNT;j++){
  67. System.out.println(counter.incrementAndGet());
  68. }
  69. //通过afterCondition通知后面线程
  70. afterCondition.signalAll();
  71. if(i<=TOTAL_PRINT_COUNT-1){
  72. try {
  73. //本线程释放锁并等待唤醒
  74. thisCondtion.await();
  75. } catch (InterruptedException e) {
  76. e.printStackTrace();
  77. }
  78. }
  79. }
  80. } finally {
  81. reentrantLock.unlock();
  82. }
  83. }
  84. }
  85. }

java多线程编程题之连续打印abc的几种解法的更多相关文章

  1. java多线程编程之连续打印abc的几种解法

    一道编程题如下: 实例化三个线程,一个线程打印a,一个线程打印b,一个线程打印c,三个线程同时执行,要求打印出10个连着的abc. 题目分析: 通过题意我们可以得出,本题需要我们使用三个线程,三个线程 ...

  2. Java多线程编程详解

    转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...

  3. Java多线程编程核心技术

    Java多线程编程核心技术 这本书有利于对Java多线程API的理解,但不容易从中总结规律. JDK文档 1. Thread类 部分源码: public class Thread implements ...

  4. 《Java多线程编程核心技术》知识梳理

    <Java多线程编程核心技术> @author ergwang https://www.cnblogs.com/ergwang/ 文章末尾附pdf和png下载链接 第1章 Java多线程技 ...

  5. Java多线程编程核心技术---学习分享

    继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...

  6. Java多线程编程核心技术---对象及变量的并发访问(二)

    数据类型String的常量池特性 在JVM中具有String常量池缓存的功能. public class Service { public static void print(String str){ ...

  7. Java多线程编程——进阶篇二

    一.线程的交互 a.线程交互的基础知识 线程交互知识点需要从java.lang.Object的类的三个方法来学习:    void notify()           唤醒在此对象监视器上等待的单个 ...

  8. 【Todo】【读书笔记】Java多线程编程指南-设计模式篇

    下了这本书<Java多线程编程指南-设计模式篇>, 还有另一本<JAVA多线程设计模式>,据说内容有重复,结合着看.

  9. Java多线程编程总结(学习博客)

    Java多线程编程总结:网址:http://lavasoft.blog.51cto.com/62575/27069/

随机推荐

  1. 【集美大学1411_助教博客】团队作业10——项目复审与事后分析(Beta版本)

    写在前面的话 软件工程课结束了,大家开心吗?是不是再也不用熬夜写代码了?如果这门课你真的熬夜写代码了,相信你一定有收获,如果这门课结束了你觉得是自己一个全新的开始,那么这门课的意义就实现了.团队作业全 ...

  2. 团队作业4——第一次项目冲刺(Alpha版本)1st day

    一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 1.界面 主界面以及游戏界面大体上完成了 界面内的功能正在写 2.登陆方面 QQ授权还未申请 申请完在登陆界面完成后实现用QQ ...

  3. 201521123002《Java程序设计》第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4-1) 1.1 实验总结 1.提交函数实 ...

  4. 201521123032 《Java程序设计》第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4-1) 1.1 实验总结 在课堂上在老师 ...

  5. 201521123106 《Java程序设计》第7周学习总结

    1. 本章学习总结 2. 书面作业 Q1.ArrayList代码分析 1.1 解释ArrayList的contains源代码 答: ArrayList的contains源代码为: public boo ...

  6. 201521123012 《Java程序设计》第十四周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 1.1建立数据库,将自己的姓名.学号作为一条记录插入.(截图, ...

  7. mysql数据库-注释相关介绍

    mysql执行的sql脚本中注释怎么写? mysql 服务器支持 # 到该行结束.-- 到该行结束 以及 /* 行中间或多个行 */ 的注释方格: mysql; # 这个注释直到该行结束 mysql; ...

  8. Ningx集群环境搭建

    Ningx集群环境搭建 Nginx是什么? Nginx ("engine x") 是⼀个⾼性能的 HTTP 和 反向代理 服务器,也是⼀个 IMAP/ POP3/SMTP 代理服务 ...

  9. markdown编辑器的学习

    markdown编辑器的学习 1 标题 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 2列表 无序列表 1 2 3 4 有序列表 1 2 3 4 3引用 这里是引用,哈哈我也不知道到我引 ...

  10. QT4.8.5 连接数据库(读写数据)

    #include "mainwindow.h" #include <QApplication> #include <QLabel> #include < ...