java多线程编程题之连续打印abc的几种解法
一道编程题如下:
实例化三个线程,一个线程打印a,一个打印b,一个打印c,三个线程同时执行,要求打印出6个连着的abc
题目分析:
通过题意我们可以得出,本题需要我们使用三个线程,三个线程分别会打印6次字符,关键是如何保证顺序一定是abc...呢。所以此题需要同步机制来解决问题!
令打印字符A的线程为ThreadA,打印B的ThreadB,打印C的为ThreadC。问题为三线程间的同步唤醒操作,主要的目的就是使程序按ThreadA->ThreadB->ThreadC-
>ThreadA循环执行三个线程,因此本人整理出了三种方式来解决此问题。
一.通过两个锁(不推荐,可读性和安全性比较差)
- /**
- * 基于两个lock实现连续打印abcabc....
- * @author fhr
- * @since 2017/09/04
- */
- public class TwoLockPrinter {
- @Test
- public void test() throws InterruptedException {
- // 打印A线程的锁
- Object lockA = new Object();
- // 打印B线程的锁
- Object lockB = new Object();
- // 打印C线程的锁
- Object lockC = new Object();
- ThreadGroup group = new ThreadGroup("xx");
- // 打印a的线程
- Thread threadA = new Thread(group, new Printer(lockC, lockA, 'A'));
- // 打印b的线程
- Thread threadB = new Thread(group, new Printer(lockA, lockB, 'B'));
- // 打印c的线程
- Thread threadC = new Thread(group, new Printer(lockB, lockC, 'C'));
- // 依次开启a b c线程
- threadA.start();
- Thread.sleep(100);
- threadB.start();
- Thread.sleep(100);
- threadC.start();
- // 主线程循环让出cpu使用权
- while (group.activeCount() > 0) {
- Thread.yield();
- }
- }
- // 打印线程
- private class Printer implements Runnable {
- // 打印次数
- private static final int PRINT_COUNT = 6;
- // 前一个线程的打印锁
- private final Object fontLock;
- // 本线程的打印锁
- private final Object thisLock;
- // 打印字符
- private final char printChar;
- public Printer(Object fontLock, Object thisLock, char printChar) {
- super();
- 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等待被唤醒
- // 必须要加判断,不然能够打印6次 但6次后就会直接死锁
- if (i < PRINT_COUNT - 1) {
- try {
- // 通过fontLock等待被唤醒
- fontLock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
- }
- }
此解法为了为了确定唤醒、等待的顺序,每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是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实现(推荐,安全性,性能和可读性较高)
- /**
- * 基于一个ReentrantLock和三个conditon实现连续打印abcabc...
- * @author fhr
- * @since 2017/09/04
- */
- public class RcSyncPrinter {
- @Test
- public void test() throws InterruptedException {
- ThreadGroup group = new ThreadGroup("xx");
- // 写锁
- 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(group, new Printer(lock, conditionA, conditionB, 'A'));
- // 实例化B线程
- Thread printerB = new Thread(group, new Printer(lock, conditionB, conditionC, 'B'));
- // 实例化C线程
- Thread printerC = new Thread(group, new Printer(lock, conditionC, conditionA, 'C'));
- // 依次开始A B C线程
- printerA.start();
- Thread.sleep(100);
- printerB.start();
- Thread.sleep(100);
- printerC.start();
- // 主线程循环让出CPU使用权
- while (group.activeCount() > 0) {
- Thread.yield();
- }
- }
- // 打印线程
- private class Printer implements Runnable {
- // 打印次数
- private static final int PRINT_COUNT = 6;
- // 打印锁
- private final ReentrantLock reentrantLock;
- // 本线程打印所需的condition
- private final Condition thisCondtion;
- // 下一个线程打印所需要的condition
- private final Condition nextCondtion;
- // 打印字符
- private final char printChar;
- public Printer(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等待被唤醒
- // 必须要加判断,不然能够打印6次 但6次后就会直接死锁
- if (i < PRINT_COUNT - 1) {
- try {
- // 本线程让出锁并等待唤醒
- thisCondtion.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- } finally {
- // 释放打印锁
- reentrantLock.unlock();
- }
- }
- }
- }
仔细想想本问题,既然同一时刻只能有一个线程打印字符,那我们为什么不使用一个同步锁ReentrantLock?线程之间的唤醒操作可以通过Condition实现,且Condition可以有
多个,每个condition.await阻塞只能通过该condition的signal/signalall来唤醒!这是synchronized关键字所达不到的,那我们就可以给每个打印线程一个自身的condition和下一
个线程的condition,每次打印字符后,调用下一个线程的condition.signal来唤醒下一个线程,然后自身再通过自己的condition.await来释放锁并等待唤醒。
三.通过一个锁和一个状态变量来实现(推荐)
- /**
- * 基于一个锁和一个状态变量实现连续打印abcabc...
- * @author fhr
- * @since 2017/09/04
- */
- public class StateLockPrinter {
- //状态变量
- private volatile int state=0;
- @Test
- public void test() throws InterruptedException {
- //锁
- Object lock=new Object();
- ThreadGroup group=new ThreadGroup("xx");
- //打印A的线程
- Thread threadA=new Thread(group,new Printer(lock, 0,1, 'A'));
- //打印B的线程
- Thread threadB=new Thread(group,new Printer(lock, 1,2, 'B'));
- //打印C的线程
- Thread threadC=new Thread(group,new Printer(lock, 2,0, 'C'));
- //一次启动A B C线程
- threadA.start();
- Thread.sleep(1000);
- threadB.start();
- Thread.sleep(1000);
- threadC.start();
- //循环检查线程组合的活的线程数量
- while (group.activeCount()>0) {
- //让出CPU使用权
- Thread.yield();
- }
- }
- //打印线程
- private class Printer implements Runnable{
- //打印次数
- private static final int PRINT_COUNT=6;
- //打印锁
- 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();
- }
- }
- }
- }
- }
状态变量是一个volatile的整型变量,0代表打印a,1代表打印b,2代表打印c,三个线程都循环检验标志位,通过阻塞前和阻塞后两次判断可以确保当前打印的正确顺序,随后线程
打印字符,然后设置下一个状态字符,唤醒其它线程,然后重新进入循环。
补充题
三个Java多线程循环打印递增的数字,每个线程打印5个数值,打印周期1-75,同样的解法:
- /**
- * 数字打印,三个线程同时打印数字,
- * 第一个线程打印12345,第二个线程打印678910 .........
- * @author fhr
- * @since 2017/09/04
- */
- public class NumberPrinter {
- //打印计数器
- private final AtomicInteger counter=new AtomicInteger(0);
- @Test
- 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();
- ThreadGroup group=new ThreadGroup("xx");
- //打印线程A
- Thread threadA=new Thread(group,new Printer(reentrantLock,conditionA, conditionB));
- //打印线程B
- Thread threadB=new Thread(group,new Printer(reentrantLock, conditionB, conditionC));
- //打印线程C
- Thread threadC=new Thread(group,new Printer(reentrantLock, conditionC, conditionA));
- // 依次开启a b c线程
- threadA.start();
- Thread.sleep(100);
- threadB.start();
- Thread.sleep(100);
- threadC.start();
- while (group.activeCount()>0) {
- Thread.yield();
- }
- }
- 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++){
- 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();
- }
- }
- }
- }
java多线程编程题之连续打印abc的几种解法的更多相关文章
- java多线程编程之连续打印abc的几种解法
一道编程题如下: 实例化三个线程,一个线程打印a,一个线程打印b,一个线程打印c,三个线程同时执行,要求打印出10个连着的abc. 题目分析: 通过题意我们可以得出,本题需要我们使用三个线程,三个线程 ...
- Java多线程编程详解
转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...
- Java多线程编程核心技术
Java多线程编程核心技术 这本书有利于对Java多线程API的理解,但不容易从中总结规律. JDK文档 1. Thread类 部分源码: public class Thread implements ...
- 《Java多线程编程核心技术》知识梳理
<Java多线程编程核心技术> @author ergwang https://www.cnblogs.com/ergwang/ 文章末尾附pdf和png下载链接 第1章 Java多线程技 ...
- Java多线程编程核心技术---学习分享
继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...
- Java多线程编程核心技术---对象及变量的并发访问(二)
数据类型String的常量池特性 在JVM中具有String常量池缓存的功能. public class Service { public static void print(String str){ ...
- Java多线程编程——进阶篇二
一.线程的交互 a.线程交互的基础知识 线程交互知识点需要从java.lang.Object的类的三个方法来学习: void notify() 唤醒在此对象监视器上等待的单个 ...
- 【Todo】【读书笔记】Java多线程编程指南-设计模式篇
下了这本书<Java多线程编程指南-设计模式篇>, 还有另一本<JAVA多线程设计模式>,据说内容有重复,结合着看.
- Java多线程编程总结(学习博客)
Java多线程编程总结:网址:http://lavasoft.blog.51cto.com/62575/27069/
随机推荐
- 【集美大学1411_助教博客】团队作业10——项目复审与事后分析(Beta版本)
写在前面的话 软件工程课结束了,大家开心吗?是不是再也不用熬夜写代码了?如果这门课你真的熬夜写代码了,相信你一定有收获,如果这门课结束了你觉得是自己一个全新的开始,那么这门课的意义就实现了.团队作业全 ...
- 团队作业4——第一次项目冲刺(Alpha版本)1st day
一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 1.界面 主界面以及游戏界面大体上完成了 界面内的功能正在写 2.登陆方面 QQ授权还未申请 申请完在登陆界面完成后实现用QQ ...
- 201521123002《Java程序设计》第8周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4-1) 1.1 实验总结 1.提交函数实 ...
- 201521123032 《Java程序设计》第8周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4-1) 1.1 实验总结 在课堂上在老师 ...
- 201521123106 《Java程序设计》第7周学习总结
1. 本章学习总结 2. 书面作业 Q1.ArrayList代码分析 1.1 解释ArrayList的contains源代码 答: ArrayList的contains源代码为: public boo ...
- 201521123012 《Java程序设计》第十四周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 1.1建立数据库,将自己的姓名.学号作为一条记录插入.(截图, ...
- mysql数据库-注释相关介绍
mysql执行的sql脚本中注释怎么写? mysql 服务器支持 # 到该行结束.-- 到该行结束 以及 /* 行中间或多个行 */ 的注释方格: mysql; # 这个注释直到该行结束 mysql; ...
- Ningx集群环境搭建
Ningx集群环境搭建 Nginx是什么? Nginx ("engine x") 是⼀个⾼性能的 HTTP 和 反向代理 服务器,也是⼀个 IMAP/ POP3/SMTP 代理服务 ...
- markdown编辑器的学习
markdown编辑器的学习 1 标题 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 2列表 无序列表 1 2 3 4 有序列表 1 2 3 4 3引用 这里是引用,哈哈我也不知道到我引 ...
- QT4.8.5 连接数据库(读写数据)
#include "mainwindow.h" #include <QApplication> #include <QLabel> #include < ...