使用两个线程,一个线程打印1-26,另一个打印A-Z,交替打印。
看到题目首先想到的就是wait notify,线程1每打印一次就等待,并唤醒线程2,线程2打印完,也等待并唤醒线程1 代码实现: 首先准备数据
public class PrintDemo { private static final int LENGTH = 26; private static int[] arrI = new int[LENGTH]; private static char[] arrC = new char[LENGTH]; //init data static{ for (int i = 0; i < LENGTH; i++) { arrI[i] = i+1; arrC[i] = (char) ('A' + i); } } //...wait notify方式:启动两个线程轮流打印。代码注意的点就是,wait 和 notify方法必须在同步块内使用,for循环结束要notify阻塞线程,否则程序不会退出。
public void printWait(){ new Thread(() -> { synchronized (this){ for (int i : arrI) { System.out.print(i); try { this.notify(); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //notify blocked Thread after print otherwise the program won't exit this.notify(); } },"t1").start(); new Thread(() -> { synchronized (this){ for (char i : arrC) { try { System.out.print(i); this.notify(); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //notify blocked Thread after print otherwise the program won't exit this.notify(); } },"t2").start(); }最终程序输出 1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z 如果要控制字母先打印,可以用CountDownLatch控制,当然也可以在t1线程把wait提到print前面,下面贴出用CountDownLatch的代码:
public void printWaitCDL(){ CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (this){ for (int i : arrI) { System.out.print(i); try { this.notify(); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //notify blocked Thread after print otherwise the program won't exit this.notify(); } },"t1").start(); new Thread(() -> { synchronized (this){ for (char i : arrC) { try { System.out.print(i); latch.countDown(); this.notify(); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //notify blocked Thread after print otherwise the program won't exit this.notify(); } },"t2").start(); }注意不能把latch.await()放到synchronized里面,否则会死锁。
原理是一样的,只不过LockSupport不用必须在synchronized里面使用,LockSupport允许精准唤醒某个线程。
利用Atomic类的可见性,也可以完成功能,思路是设置一个共享变量控制当前轮到哪个线程执行。
static volatile boolean t1turn = true; static AtomicInteger count = new AtomicInteger(0); void printAtomic(){ new Thread(()->{ while (count.get()<LENGTH){ while (t1turn){ System.out.print(arrI[count.get()]); t1turn = false; } } },"t1").start(); new Thread(()->{ while (count.get()<LENGTH){ while (!t1turn){ System.out.print(arrC[count.getAndIncrement()]); t1turn = true; } } },"t2").start(); }这里设置了两个共享变量,一个用于控制线程执行权,一个用作循环的计数。这里volatile和Atomic可以起到同样的效果,关键就是保证变量的可见性。
上面的实现方式可用一个忙循环改造一下。
enum Turn {T1,T2} volatile Turn turn = Turn.T1; void printVolatile(){ new Thread(()->{ for (int i : arrI) { while (turn != Turn.T1){} System.out.print(i); turn = Turn.T2; } },"t1").start(); new Thread(()->{ for (char i : arrC) { while (turn != Turn.T2){} System.out.print(i); turn = Turn.T1; } },"t2").start(); }以上两种方式区别就是不进行线程挂起,通过循环判断volatile共享变量的状态来实现交替打印,实际上并没有线程通信。
wait notify + synchronized 这一套也可以用 ReentrantLock的Condition来代替:
ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); void printLockCondition(){ new Thread(()->{ try { lock.lock(); for (int i : arrI) { System.out.print(i); condition.signal(); condition.await(); } } catch (InterruptedException e) { e.printStackTrace(); }finally { //let other Thread run, and unlock condition.signal(); lock.unlock(); } },"t1").start(); new Thread(()->{ try { lock.lock(); for (char i : arrC) { System.out.print(i); condition.signal(); condition.await(); } } catch (InterruptedException e) { e.printStackTrace(); }finally { condition.signal(); lock.unlock(); } },"t2").start(); }可以用两个Condition,优化一下Condition的实现方式,实现精准唤醒某个线程。
ReentrantLock lock2 = new ReentrantLock(); Condition conditionT1 = lock2.newCondition(); Condition conditionT2 = lock2.newCondition(); void printLock2Condition(){ new Thread(()->{ try { lock.lock(); for (int i : arrI) { System.out.print(i); conditionT2.signal(); conditionT1.await(); } } catch (InterruptedException e) { e.printStackTrace(); }finally { //let other Thread run, and unlock conditionT2.signal(); lock.unlock(); } },"t1").start(); new Thread(()->{ try { lock.lock(); for (char i : arrC) { System.out.print(i); conditionT1.signal(); conditionT2.await(); } } catch (InterruptedException e) { e.printStackTrace(); }finally { conditionT1.signal(); lock.unlock(); } },"t2").start(); }要实现线程阻塞自然可以想到阻塞队列,利用BlockingQueue的take阻塞方法,也可以实现两个线程交替输出。
static BlockingQueue<Integer> q1 = new ArrayBlockingQueue<>(1); static BlockingQueue<Character> q2 = new ArrayBlockingQueue<>(1); void printBlockingQueue(){ new Thread(()->{ for (int i : arrI) { try { q1.put(i); System.out.print(q2.take());//blocked } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); new Thread(()->{ for (char i : arrC) { try { System.out.print(q1.take()); q2.put(i); } catch (InterruptedException e) { e.printStackTrace(); } } },"t2").start(); }BlockingQueue的take方法是获取的时候阻塞,还可以用TransferQueue的transfer方法,放入就阻塞,直到元素被取走。
static TransferQueue<String> tq = new LinkedTransferQueue<>(); void printTransferQueue(){ new Thread(()->{ for (int i : arrI) { try { tq.transfer(String.valueOf(i)); System.out.print(tq.take()); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); new Thread(()->{ for (char i : arrC) { try { System.out.print(tq.take()); tq.transfer(String.valueOf(i)); } catch (InterruptedException e) { e.printStackTrace(); } } },"t2").start(); }两个线程交替执行,可以利用wait notify方法,volatile变量线程可见性,Lock API,LockSupport的park unpark方法,阻塞队列API以及其他方式来实现。