1. 固定运行顺序
1.1 wait notify 版
@Slf4j(topic = "c.Test25")
public class Test25 {
// 用来同步的对象
static final Object lock = new Object();
// 表示 t2 是否运行过
static boolean t2runned = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
// 等待t2线程运行
while (!t2runned) {
try {
// t1 先等一会
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock) {
log.debug("2");
t2runned = true;
lock.notify();
}
}, "t2");
t1.start();
t2.start();
}
}
输出
16:41:58.698 c.Test25 [t2] - 2
16:41:58.699 c.Test25 [t1] - 1
1.2 Park Unpark 版
可以看到,实现上很麻烦:
首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 wait
第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决此问题
最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
可以使用 LockSupport 类的 park 和 unpark 来简化上面的题目:
@Slf4j(topic = "c.Test26")
public class Test26 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
// 当没有『许可』时,当前线程暂停运行;
// 有『许可』时,用掉这个『许可』,当前线程恢复运行
LockSupport.park();
log.debug("1");
}, "t1");
t1.start();
new Thread(() -> {
log.debug("2");
// 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』)
LockSupport.unpark(t1);
},"t2").start();
}
}
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要『同步对象』和『运行标记』
输出
16:41:58.698 c.Test26 [t2] - 2
16:41:58.699 c.Test26 [t1] - 1
2. 交替输出
线程 1 输出 a 3 次,线程 2 输出 b 3 次,线程 3 输出 c 3 次。现在要求输出 abcabcabc 怎么实现
2.1 wait notify 版
public class ExchangePrintTest {
public static void main(String[] args) {
WaitNotify waitNotify = new WaitNotify(1,3);
new Thread(()->{
waitNotify.print("a",1,2);
}).start();
new Thread(()->{
waitNotify.print("b",2,3);
}).start();
new Thread(()->{
waitNotify.print("c",3,1);
}).start();
}
}
class WaitNotify {
/**
* 等待标记
*/
private int flag;
/**
* 循环次数
*/
private final int next;
public WaitNotify(int flag, int next) {
this.flag = flag;
this.next = next;
}
/**
* 打印
* @param str 打印字符串
* @param waitFlag 等待标记
* @param nextFlag 下一个标记
*/
public void print(String str, int waitFlag, int nextFlag){
for (int i = 0; i < next; i++) {
synchronized (this) {
// 当前标记不等于下一步标记
while (this.flag != waitFlag) {
try {
// 该线程等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flag = nextFlag;
// 所有线程唤醒
this.notifyAll();
}
}
}
}
abcabcabc
2.2 Lock 条件变量版
和上面的逻辑是一样的,只是将标识符改为Condition(条件变量),然后使用了Lock锁。
public class ExchangePrintLockTest {
public static void main(String[] args) {
AwaitSignal awaitSignal = new AwaitSignal(3);
Condition a1 = awaitSignal.newCondition();
Condition a2 = awaitSignal.newCondition();
Condition a3 = awaitSignal.newCondition();
new Thread(() -> {
awaitSignal.print("a", a1, a2);
}).start();
new Thread(() -> {
awaitSignal.print("b", a2, a3);
}).start();
new Thread(() -> {
awaitSignal.print("c", a3, a1);
}).start();
// 因为前面的锁,都在睡眠,必须先唤醒a1线程
awaitSignal.lock();
try {
Thread.sleep(1000);
System.out.println("开始...");
a1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock {
private final int loopNumber;
public AwaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str, Condition current, Condition next) {
for (int i = 0; i < loopNumber; i++) {
lock();
try {
current.await();
System.out.print(str);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
开始...
abcabcabc
进阶写法
public class ExchangePrintLockTestV2 {
public static void main(String[] args) {
AwaitSignal2 as = new AwaitSignal2(3);
as.start(new Thread(() -> {
as.print("a");
}), new Thread(() -> {
as.print("b");
}), new Thread(() -> {
as.print("c");
}), new Thread(() -> {
as.print("d");
}));
}
}
class AwaitSignal2 extends ReentrantLock {
private final int loopNumber;
private Map<Thread, Condition[]> map =new ConcurrentHashMap<>();
public AwaitSignal2(int loopNumber) {
this.loopNumber = loopNumber;
}
public void start(Thread... threads) {
Condition[] temp = new Condition[threads.length];
int threadsLength = threads.length;
// 创建线程数的标记
for (int i = 0; i < threadsLength; i++) {
temp[i] = this.newCondition();
}
// 必须把前面的线程组全部都加入了,才能进行获取线程组中的标记
for (int i = 0; i < threadsLength; i++) {
// 条件标记
Condition current = temp[i];
// 下一个标记
Condition next;
// 2、3、1
if (i == threadsLength -1){
next = temp[0];
} else {
next = temp[i + 1];
}
// 对应的线程拥有的标记
map.put(threads[i], new Condition[]{current, next});
}
// 启动每个线程
for (Thread thread : map.keySet()) {
thread.start();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的代码,也是要先唤醒第一个线程,否则进入await就会一直睡眠
this.lock();
try {
// 唤醒第一个线程
map.get(threads[0])[0].signal();
}finally {
this.unlock();
}
}
public void print(String str){
for (int i = 0; i < loopNumber; i++) {
this.lock();
try {
Condition[] conditions = map.get(Thread.currentThread());
conditions[0].await();
System.out.print(str);
conditions[1].signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
this.unlock();
}
}
}
}
2.3 Park Unpark 版
public class Test31 {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) {
ParkUnpark pu = new ParkUnpark(5);
t1 = new Thread(() -> {
pu.print("a", t2);
});
t2 = new Thread(() -> {
pu.print("b", t3);
});
t3 = new Thread(() -> {
pu.print("c", t1);
});
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
}
class ParkUnpark {
public void print(String str, Thread next) {
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(next);
}
}
private int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
}