问题描述:启动三个线程,分别负责打印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多线程循环依次输出abc_等待状态

思路:启动三个线程,分别命名为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);
	}
}




这样就能保证线程在启动时有顺序了.

附上一张图片,帮助理解:


java在循环中调用线程池来处理 java多线程循环依次输出abc_等待状态_02




三.第三种方法


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