Java|Java 线程经典问题,三个线程,循环打印ABCABCABC 的多种实现方法

问题描述:启动三个线程,分别负责打印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 的多种实现方法】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); } }





这样就能保证线程在启动时有顺序了.
附上一张图片,帮助理解:

Java|Java 线程经典问题,三个线程,循环打印ABCABCABC 的多种实现方法
文章图片





三.第三种方法

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来控制



    推荐阅读