面试题目(两个线程交替输出字符-线程间通信)

最近学习多线程和锁方面的知识,偶然看到马士兵老师对于题目这道面试题的解析,觉得对自己学习多线程很有帮助,所以把其中个人觉得比较优雅和常用的方式代码写下来以备记录。
题目大概是这样的:要求新建两个线程,使得这两个线程依次输出:1A2B3C4D5E6F....,这里给出三种不同的解决方法。

import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.LockSupport; /** * 两个线程之间通讯 * 交替输出每个线程变量的值 * eg:1A2B3C4D5E6F... * @author admin * @DATE 2020/6/20 */ public class TwoThreadCommunication { // 定义两个线程 static Thread t1,t2; static char[] iArray = "1234567".toCharArray(); static char[] cArray = "ABCDEFG".toCharArray(); enum MONITOR{MONITOR1,MONITOR2}; static volatile MONITOR m = MONITOR.MONITOR1; // 这里m变量必须为volatile,不然自旋可能会出问题 final static Object obj = new Object(); public static void main(String[] args) { //parkAndUnpark(); //compareAndSet(); standardComm(); } }

第一 种:利用locksupport线程工具类的park和unpark方法,使得线程之间互相阻塞等待和启动唤醒。park方法表示当前线程停止执行,直到其他线程显示调用unpark方法唤醒自己,这个unpark 方法可以指定唤醒具体哪个参数,这是不同于notify的,notify是随机唤醒队列里的某个线程,不能确定具体唤醒哪个。
/* 使用LockSupport unpark/park 启动、暂停线程 */ static void parkAndUnpark(){ t1 = new Thread( () -> { for (char i : iArray){ System.out.print(i); LockSupport.unpark(t2); LockSupport.park(); } },"t1"); t2 = new Thread(() -> { for( char c : cArray){ LockSupport.park(); // 可以在线程没有获取到cpu的情况下调用park,不同于notify方法必须当前线程正在运行,此处代码能保证优先输出t1里面的char。 System.out.print(c); LockSupport.unpark(t1); } },"t2"); t1.start(); t2.start(); }

第二种:利用代码实现cas自旋锁,在获取到cpu之前,线程一直自旋无任何操作等待,直到满足条件,退出自旋执行具体业务代码。cas方式仅停留在jvm层面,也就是在用户态进行自旋,并不会进入内核态,提高了运行速度,但是由于一直在自旋,会占用cpu,所以cas适用于线程数较少并且业务逻辑代码耗时较少的环境。
cas需要二要素:1、自旋(不进行任何操作),2、结束自旋的条件,这个条件可以是各种判断,但必须是线程安全的判断,因为如果线程不安全,则可能会永远跳不出自旋条件。
/* 使用自旋锁,不断自旋直到本线程得到cpu调度 */ static void compareAndSet(){ new Thread(() -> { for(char i : iArray){ while ( m != MONITOR.MONITOR1){}; // while循环空实现,进行自旋 System.out.print(i); m = MONITOR.MONITOR2; } },"t1").start(); new Thread(() -> { for(char i : cArray){ while ( m != MONITOR.MONITOR2){}; System.out.print(i); m = MONITOR.MONITOR1; } },"t2").start(); }

【面试题目(两个线程交替输出字符-线程间通信)】其中,m为自旋结束条件,可以在定义处看到,m为线程安全的 volatile。
第三种:利用标准线程间 通信的方法:notify、notifyAll、wait等方法。
/* 使用标准线程间通讯方法 notify、wait */ public static void standardComm(){ CountDownLatch cdl = new CountDownLatch(1); // 利用countdownlatch定义线程间通讯并且保证线程t1先执行 new Thread( () -> { cdl.countDown(); synchronized (obj){ for(char c : iArray){ System.out.print(c); try { obj.notify(); obj.wait(); }catch (Exception e){ e.printStackTrace(); } } obj.notify(); } },"t1").start(); new Thread( () -> { try{cdl.await(); }catch(Exception e){} synchronized (obj){ for(char i : cArray){ System.out.print(i); try { obj.notify(); obj.wait(); }catch (Exception e){} } obj.notify(); } },"t2").start(); }

notify方法与LockSupport的unpark方法相比,在当前线程持有锁的时候调用,能够唤醒等待队列的随机一个线程,不能保证唤醒指定的线程,并且是重量级锁定义,由于在等待队列之间切换也会比较消耗性能。我们可以看到,在代码中,为了保证t1线程的输出优先于t2的输出,定义了一个CountDownLatch类,这个类的构造器传入一个int值,一旦调用此类实例的countdown方法,这个int值就减1,直到为0,则调用了此实例awake方法的所有线程会同时被唤醒,此类主要用于线程间先后顺序使用,一般不推荐线程的setPriority方法。
由于初学多线程方面的,代码或者文字阐述有任何问题欢迎大神评论指教,不胜感激。

    推荐阅读