问题描述:启动三个线程,分别负责打印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();
}
}
思路:启动三个线程,分别命名为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); // 当前前程任务执行完毕,将其上一个线程的<span style="white-space:pre"> </span> // 锁释放,等待需要该锁的的线程获取该锁
} 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);
}
}
这样就能保证线程在启动时有顺序了.
附上一张图片,帮助理解:
三.第三种方法
<pre name="code" class="java"> 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来控制