我是少侠露飞。学习塑造人生,技术改变世界。
文章目录
- 前言
- 面试题分析
- Object的wait/notify方式
- 通过一个volatile类型的变量控制
- 通过原子变量AtomicInteger和闭锁CountDownLatch实现
前言
Java多线程这块是企业面试的热门知识点,面试官也喜欢让候选人手写部分代码,主要为了考察候选人对线程同步机制的理解及使用熟练度。诸如启动两个线程交替打印1~100的奇偶数这种问题张口就来。
面试题分析
其实交替打印奇、偶数就是想启动两个线程实现以下效果:
奇线程:1
偶线程:2
……
奇线程:99
偶线程:100
简单分析一下,首先是两个线程,其次是交替顺序打印。这可以联系到线程之间的通信问题。这时可以想到大致的方向就是加锁,哪个线程拿到锁就打印,然后释放锁让另一个线程获取锁。两个线程轮流拿到锁,实现交替打印的效果。
Object的wait/notify方式
线程通信首先大家能想到的应该是Object类的wait()/notify()机制。
当有多个线程处理等待状态时,notify()是随机唤醒其中一个,如果想唤醒所有,则应该调用notifyAll()方法。
此时我们应该定义两个线程,一个打印奇数,一个打印偶数,两个线程持有同一把对象锁(可以通过构造方法注入),这样就能保证线程之间的同步了。这里有三点需要注意:
- 共享变量number在两个线程中通过获取到锁之后才能进行操作,我们都知道
++
操作是非原子性的,但是本例中通过锁保证同一时刻只有一个线程可以操作,所以无需使用AtomicInteger
这类的原子变量。 - 对于奇线程,若当前数字是偶数时(偶线程相反),就调用wait()方法阻塞等待,如果是奇数,则执行打印,并接着自增,然后通过notify()唤醒另一个线程。
- 若上一步唤醒了线程之后,其实现在奇、偶线程都是在争抢CPU执行权的,如果还是被奇线程抢到了锁,但是判断当前number是偶数,所以会调用wait()操作进入等待。
public class Main {
private static volatile int number = 1;
private static final int MAX = 100;
public static void main(String[] args) {
Object monitior = new Object();
Thread oddThread = new Thread(new OddThread(monitior));
oddThread.setName("奇线程");
Thread evenThread = new Thread(new EvenThread(monitior));
evenThread.setName("偶线程");
oddThread.start();
evenThread.start();
}
/**
* 奇数线程
*/
static class OddThread implements Runnable {
private Object monitor;
public OddThread(Object monitor) {
this.monitor = monitor;
}
@Override
public void run() {
while (number < MAX) {
synchronized (monitor) {
while (number % 2 == 0) {
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
monitor.notify();
}
}
}
}
/**
* 偶数线程
*/
static class EvenThread implements Runnable {
private Object monitor;
public EvenThread(Object monitor) {
this.monitor = monitor;
}
@Override
public void run() {
while (number <= MAX) {
synchronized (monitor) {
while (number % 2 != 0) {
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
monitor.notify();
}
}
}
}
}
通过一个volatile类型的变量控制
该方式的思路是,线程在volatile变量上无限循环,直到volatile变量变为false。变为false后,线程开始真正地执行业务逻辑,打印数字,最后,需要挂起自己,并修改volatile变量,来唤醒其他线程。
public class Main {
private static volatile int number = 1;
private static final int MAX = 100;
private static volatile boolean flag = true;
public static void main(String[] args) {
Thread oddThread = new Thread(new OddThread());
oddThread.setName("奇线程");
Thread evenThread = new Thread(new EvenThread());
evenThread.setName("偶线程");
oddThread.start();
evenThread.start();
}
/**
* 奇数线程
*/
static class OddThread implements Runnable {
@Override
public void run() {
while (number < MAX) {
while (flag) {
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
flag = false;
}
}
}
}
/**
* 偶数线程
*/
static class EvenThread implements Runnable {
@Override
public void run() {
while (number <= MAX) {
while (!flag) {
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
flag = true;
}
}
}
}
}
通过原子变量AtomicInteger和闭锁CountDownLatch实现
public class Main {
private static ExecutorService executorService = new ThreadPoolExecutor(2, 5, 1000L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024));
private static CountDownLatch countDownLatch = new CountDownLatch(2);
private static volatile boolean flag = false;
private static AtomicInteger num = new AtomicInteger(1);
private static final Integer MAX = 100;
public static void main(String[] args) throws InterruptedException {
executorService.submit(new Runnable() {
@Override
public void run() {
while (num.get() <= MAX ) {
if (!flag) {
System.out.println(Thread.currentThread().getName() + "-->" + num.getAndIncrement());
flag = true;
}
}
countDownLatch.countDown();
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
while (num.get() <= MAX) {
if (flag) {
System.out.println(Thread.currentThread().getName() + "-->" + num.getAndIncrement());
flag = false;
}
}
countDownLatch.countDown();
}
});
countDownLatch.await();
}
}
我是少侠露飞,咱们下期见。