问题描述:启动三个线程,分别负责打印A,B,C,要求按ABCABC...如此顺序打印10遍.
首先,我们来分析分析这个问题,要求循环打印10遍ABC,并且要指定线程负责打印对应的字母,这里主要的问题就是线程执行中,cpu是随机分配给线程的,那怎样控制他们的打印顺序?下面就介绍两种不同的方法来打印他们.
一.第一种方式
public class LoopChar {
/**
* 创建三个线程,循环打印ABCABCABC
* @author lvliang
*/
static class LoopI implements Runnable {
private int id;
private static int index = 1;
private final int end = 30;
private char[] ch = { 'A', 'B', 'C' };
public LoopI(int id) {
this.id = id;
}
public void run() {
while (index <= end) {
synchronized (Class.class) {
if ((index + 3 - 1) % 3 == id) {
System.out.println(id + "-" + ch[(index + 3 - 1) % 3]
+ " -" + index);
index++;
Class.class.notifyAll();
} else {
try {
Class.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new LoopI(0)).start();
new Thread(new LoopI(1)).start();
new Thread(new LoopI(2)).start();
}
}
【Java|Java 线程经典问题,三个线程,循环打印ABCABCABC 的多种实现方法】
文章图片
思路:启动三个线程,分别命名为0,1,2.定义一个数组,里面存放的是需要打印出来的A,B,C.当三个线程启动过后,index变量作为标记,当轮到自己时,就打印,(index + 3 - 1) % 3)这句话会根据当前index算出该谁来打印.当前线程打印完后,会先释放锁,唤醒所有处于等待的线程(notifyAll),等待中的线程别唤醒后,检测是否该自己打印,是的话进入打印,否则继续wait.
二.第二种方法
public class LoopChar { static class LoopII implements Runnable {
private static int index = 1;
private static final int end = 30;
private char id;
private Object pre;
// 前一个线程持有的锁
private Object self;
// 当前线程持有的锁public LoopII(char id, Object pre, Object self) {
this.id = id;
this.pre = pre;
this.self = self;
}public void run() {
while (index++ <= end) {
synchronized (pre) {
synchronized (self) {
System.out.print(id);
if ('C' == id && index < end) {
System.out.print("-");
}
self.notify();
// 释放自身的锁,唤醒需要该锁的线程
}
try {
pre.wait(100);
// 当前前程任务执行完毕,将其上一个线程的// 锁释放,等待需要该锁的的线程获取该锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Objet();
Object obj2 = new Object();
Object obj3 =new Objet();
new Thread(new LooI('A',obj3, obj1)).start();
new Thread(new LoopII('B', obj1, obj2)).start();
new Thread(new LoopII('C', obj2, obj3)).start();
}
}
思路:同样,启动三个线程,这里我们创建三把锁,每个线程持有两把锁,分别是pre锁(前个线程的锁),self锁(自身线程的锁),只有当满足同时拥有两把锁时,线程才能运行,当前线程执行完毕后,首先释放自己的锁,并唤醒正在等待这把锁的线程(也就是他的下个线程),然后将前个线程的锁释放,该线程进入等待.比如A线程,持有self,pre两把锁,A执行完后就将self锁释放,由于此时B线程正在等待他的前个线程的锁,这时B就会获得A的self锁,也就是B的pre锁,此时B的self锁还未被其他线程占用,B线程满足条件,进入执行,同理,B执行完后,释放self锁,唤醒C线程,C取得自己的self锁,进入执行,完后,释放自身self锁,唤醒A线程......如此往复.
细心的人会发现,这样还并不能保证能按照我们的与预期进行,比如,main()方法中创建A线程时,A获得其前置锁,也就是obj3,然后获取自身锁obj1然后进入执行,但是当A线程正在执行还未执行到slef.notiffy()时,这是cpu又切换至主线程中创建B线程,这时B线程就会在synchronized (pre)处等待(因为A线程还未释放自身锁),接着C线程被创建,进入执行,没有任何问题,打印C,这是就会出现ACBACB现象,怎么解决呢?那就让这三个线程按顺序启动,如下:
public class LoopChar { static class LoopII implements Runnable {
private static int index = 1;
private static final int end = 30;
private char id;
private Object pre;
// 前一个线程持有的锁
private Object self;
// 当前线程持有的锁public LoopII(char id, Object pre, Object self) {
this.id = id;
this.pre = pre;
this.self = self;
}public void run() {
while (index++ <= end) {
synchronized (pre) {
synchronized (self) {
System.out.print(id);
if ('C' == id && index < end) {
System.out.print("-");
}
self.notify();
// 释放自身的锁,唤醒需要该锁的线程
}
try {
pre.wait(100);
// 当前前程任务执行完毕,将其上一个线程的// 锁释放,等待需要该锁的的线程获取该锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Objet();
Object obj2 = new Object();
Object obj3 =new Objet();
new Thread(new LooI('A',obj3, obj1)).start();
Thread.sleep(1);
new Thread(new LoopII('B', obj1, obj2)).start();
Thread.sleep(1);
new Thread(new LoopII('C', obj2, obj3)).start();
Thread.sleep(1);
}
}
这样就能保证线程在启动时有顺序了.
附上一张图片,帮助理解:
文章图片
三.第三种方法
static class LoopIV {
private static Lock lock = new ReentrantLock();
public int index = 1;
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void sub1(char c) {
lock.lock();
try {
while (index != 1) {
try {
condition1.await();
//线程一进入等待状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.print(c);
index = 2;
//将标记改为2,让线程二满足运行条件
condition2.signal();
//唤醒线程二,说你的条件满足了(index==2),你可以运行了
} finally {
lock.unlock();
}
}public void sub2(char c) {
lock.lock();
try {
while (index != 2) {
try {
condition2.await();
//线程二进入等待状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.print(c);
index = 3;
//将标记改为3,让线程三满足运行条件
condition3.signal();
//唤醒线程三,说你的条件满足了(index==3),你可以运行了
} finally {
lock.unlock();
}
}public void sub3(char c) {
lock.lock();
try {
while (index != 3) {
try {
condition3.await();
//线程三进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(c+"-");
index = 1;
//将标记改为1,让线程一满足运行条件
condition1.signal();
//唤醒线程一,说你的条件满足了(index==1),你可以运行了
} finally {
lock.unlock();
}
}
} public static void main(String[] args) throws InterruptedException {
final LoopIV iv = new LoopIV();
new Thread(new Runnable() {@Override
public void run() {
for (int i = 0;
i < 10;
i++) {
iv.sub1('A');
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;
i < 10;
i++) {
iv.sub2('B');
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;
i < 10;
i++) {
iv.sub3('C');
}
}
}).start();
}
}
解析,这种解决方法采用了Java并发库自带的Lock&Condition类,Condition的创建必须依赖于一把锁Lock.
此问题中三个线程必须共用一把锁,才能保证线程间的运行正确并且能相互通信,
condition1,condition2,condition3相当于将线程管理起来,什么时候wait,什么时候run,都通过condition来控制
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)