前言
一道面试题,两个线程交叉打印奇偶数。核心是多线程交替切换。
1. synchronized锁机制,wait和notifyAll方法
本质锁竞争
仅在线程需要的时候持有锁,其余时间检查自身线程锁,释放线程自己持有的锁。
public class Test2 {
public static void main(String[] args) {
Num num = new Num();
//线程1-奇数线程
new Thread(() -> {
while (num.number < 100){
synchronized (num){
//奇数
if (num.number%2 == 1){
System.out.println(Thread.currentThread().getName() + "\t奇数线程:" + num.number);
num.number++;
num.notifyAll();
} else {
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
//线程2-偶数线程
new Thread(() -> {
while (num.number < 100){
synchronized (num){
//奇数
if (num.number%2 == 1){
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + "\t偶数线程:" + num.number);
num.number++;
num.notifyAll();
}
}
}
}).start();
}
static class Num {
public int number = 1;
}
}
代码很简单,通过wait和notifyAll方法来释放自身线程锁和让其他线程持有锁。
这里要注意:使用synchronized加锁必须是对象,synchronized就是通过对象头的方式加锁的。
2. ReentrantLock实现
public class Test21 {
public static void main(String[] args) {
Num num = new Num();
ReentrantLock lock = new ReentrantLock();
//奇数条件
Condition oddCondition = lock.newCondition();
//偶数条件
Condition evenCondition = lock.newCondition();
//线程1-奇数线程
new Thread(() -> {
while (num.number < 100) {
lock.lock();
try {
//奇数
if (num.number % 2 == 1) {
System.out.println(Thread.currentThread().getName() + "\t奇数线程:" + num.number);
num.number++;
evenCondition.signal();
} else {
oddCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
//线程2-偶数线程
new Thread(() -> {
while (num.number < 100) {
lock.lock();
try {
//奇数
if (num.number % 2 == 1) {
evenCondition.await();
} else {
System.out.println(Thread.currentThread().getName() + "\t偶数线程:" + num.number);
num.number++;
oddCondition.signal();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
}
static class Num {
public int number = 1;
}
}
ReentrantLock的condition控制更加精准,可以细节到具体的线程
ReentrantLock可以对每一个线程设置一个Condition,控制到具体的单个线程,是notify所不具备的。
3. 总结
这里的思想在RPC框架中非常常用。
1. 发起RPC的调用的时候,程序不会立马返回,需要将自身线程等待
2. 调用结果返回时,需要唤醒自身线程。
3. 通知机制,这里使用notifyAll通知,而RPC一般使用观察者模式,回调通知。