点击上方蓝色“趣学程序”,选择“设为星标”
回复“资源”获取独家整理的学习资料!
回复“加群”与更多小伙伴共同成长!
回复“源码”获取专属项目源码!
前提
今天下班时候和同事聊天偶然听到面试题“两个线程交替打印奇数和偶数”的实现,这里做一个复盘。
复盘
场景一:线程A打印奇数,线程B打印偶数,线程A和线程B交替打印,使用对象监视器实现。
场景二:线程A打印奇数,线程B打印偶数,线程A和线程B交替打印,使用JDK提供的并发类库实现。
这两个场景中,场景一是一种比较古老的同步方式,本质由JVM实现;场景二是JDK1.5引入JUC包之后简化了并发编程的前提下的更简便的实现。下面针对两个场景做对应的实现。
场景一
场景一中,线程A和线程B交替打印奇数和偶数,使用对象监视器实现,通俗来说:线程A或线程B只要有一者竞争锁成功,就打印++i,通知其他线程从等待集合中释放,然后自身线程加入等待集合并且释放锁即可。
public class OddEvenPrinter {
private final Object monitor = new Object();
private final int limit;
private volatile int count;
public OddEvenPrinter(int limit, int initCount) {
this.limit = limit;
this.count = initCount;
}
public void print() {
synchronized (monitor) {
while (count < limit) {
try {
System.out.println(String.format("线程[%s]打印数字:%d", Thread.currentThread().getName(), ++count));
monitor.notifyAll();
monitor.wait();
} catch (InterruptedException e) {
//ignore
}
}
}
}
public static void main(String[] args) throws Exception {
OddEvenPrinter printer = new OddEvenPrinter(10, 0);
Thread thread1 = new Thread(printer::print, "thread-1");
Thread thread2 = new Thread(printer::print, "thread-2");
thread1.start();
thread2.start();
Thread.sleep(Integer.MAX_VALUE);
}
}
执行后的输出结果:
线程[thread-1]打印数字:1
线程[thread-2]打印数字:2
线程[thread-1]打印数字:3
线程[thread-2]打印数字:4
线程[thread-1]打印数字:5
线程[thread-2]打印数字:6
线程[thread-1]打印数字:7
线程[thread-2]打印数字:8
线程[thread-1]打印数字:9
线程[thread-2]打印数字:10
场景二
场景二中,如果需要使用JUC中提供的并发类库,可以考虑和对象监视器功能接近的可重入锁ReentrantLock
。具体代码如下:
public class OddEvenPrinterEx {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private final int limit;
private volatile int count;
public OddEvenPrinterEx(int limit, int initCount) {
this.limit = limit;
this.count = initCount;
}
public void print() {
lock.lock();
try {
while (count < limit){
System.out.println(String.format("线程[%s]打印数字:%d", Thread.currentThread().getName(), ++count));
condition.signalAll();
try {
condition.await();
} catch (InterruptedException e) {
//ignore
}
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws Exception {
OddEvenPrinterEx printer = new OddEvenPrinterEx(10, 0);
Thread thread1 = new Thread(printer::print, "thread-1");
Thread thread2 = new Thread(printer::print, "thread-2");
thread1.start();
thread2.start();
Thread.sleep(Integer.MAX_VALUE);
}
}
执行后的输出结果:
线程[thread-2]打印数字:1
线程[thread-1]打印数字:2
线程[thread-2]打印数字:3
线程[thread-1]打印数字:4
线程[thread-2]打印数字:5
线程[thread-1]打印数字:6
线程[thread-2]打印数字:7
线程[thread-1]打印数字:8
线程[thread-2]打印数字:9
线程[thread-1]打印数字:10
眼尖的可能看到这里是先由thread-2打印奇数,然后thread-1打印偶数,这个和同步器框架的等待队列以及同步队列的竞争有关。
小结
这个问题有很多种解决思路,但是目前笔者没想到无锁实现方案。很多现成的(参考多个博客)方案里面都是使用各种多重同步或者加锁,其实意义是不大,实际上要理解对象监视器和同步器框架AQS的一些原理,那么实现起来自然比较简单。参看笔者之前写的两篇文章:
深入理解Object提供的阻塞和唤醒API
JUC同步器框架AbstractQueuedSynchronizer源码图文分析
往期推荐
Mybatis面试18问,你想知道的都在这里了!牛,微信支付架构竟然是这么实现的请你详细说说类加载流程,类加载机制及自定义类加载器说一下HashMap的实现原理?Lombok天天用,却不知道它的原理是什么?
扫描二维码
获取更多精彩
趣学程序