线程通信的一百种写法

    技术2022-07-11  109

    文章目录

    题目方式1:wait notify方式2:LockSupport方式3:Atomic方式4:忙循环方式5:ReentrantLock Condition方式6:ReentrantLock 2 Condition方式7:BlockingQueue方式8:TransferQueue总结 线程通信是多线程应用开发中最常见的操作,除了wait notify方法,jdk提供了多种多样的api可以实现线程通信。 下面通过一个简单的题目看看jdk的多种方式实现线程通信。

    题目

    使用两个线程,一个线程打印1-26,另一个打印A-Z,交替打印。

    方式1:wait notify

    看到题目首先想到的就是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里面,否则会死锁。

    方式2:LockSupport

    Thread t1 = null , t2 = null; public void printLockSupport(){ t1 = new Thread(()->{ for (int i : arrI) { System.out.print(i); LockSupport.unpark(t2); LockSupport.park(); } },"t1"); t2 = new Thread(()->{ for (char i : arrC) { LockSupport.park(); System.out.print(i); LockSupport.unpark(t1); } },"t1"); t1.start(); t2.start(); }

    原理是一样的,只不过LockSupport不用必须在synchronized里面使用,LockSupport允许精准唤醒某个线程。

    方式3:Atomic

    利用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可以起到同样的效果,关键就是保证变量的可见性。

    方式4:忙循环

    上面的实现方式可用一个忙循环改造一下。

    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共享变量的状态来实现交替打印,实际上并没有线程通信。

    方式5:ReentrantLock Condition

    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(); }

    方式6:ReentrantLock 2 Condition

    可以用两个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(); }

    方式7:BlockingQueue

    要实现线程阻塞自然可以想到阻塞队列,利用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(); }

    方式8:TransferQueue

    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以及其他方式来实现。

    Processed: 0.020, SQL: 10