最近学习多线程和锁方面的知识,偶然看到马士兵老师对于题目这道面试题的解析,觉得对自己学习多线程很有帮助,所以把其中个人觉得比较优雅和常用的方式代码写下来以备记录。
题目大概是这样的:要求新建两个线程,使得这两个线程依次输出: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方法。
由于初学多线程方面的,代码或者文字阐述有任何问题欢迎大神评论指教,不胜感激。