1、固定运行顺序
比如,必须先 2 后 1 打印
1.1、wait notify 版
public class Test1 {
// 先打印2,后打印1
static final Object lock = new Object();
static boolean t2runned = false; //表示t2是否运行过
public static void main(String[] args) {
Thread t1 = new Thread(()->{
synchronized (lock){
while(!t2runned){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(Thread.currentThread().getName() + ": " + "1");
}, "t1");
Thread t2 = new Thread(()->{
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + ": " + "2");
t2runned = true;
lock.notifyAll();
}
}, "t2");
t1.start();
t2.start();
}
}
1.2、Park Unpark 版
可以看到,实现上很麻烦:
● 首先,需要保证先 wait 再 notify,否则 wait 线程永远得不到唤醒。因此使用了『运行标记』来判断该不该 wait 。
● 第二,如果有些干扰线程错误地 notify 了 wait 线程,条件不满足时还要重新等待,使用了 while 循环来解决 此问题。
● 最后,唤醒对象上的 wait 线程需要使用 notifyAll,因为『同步对象』上的等待线程可能不止一个
可以使用 LockSupport 类的 park 和 unpark 来简化上面的题目:
import java.util.concurrent.locks.LockSupport;
public class Test2 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
LockSupport.park();
System.out.println(Thread.currentThread().getName() + ": " + "1");
},"t1");
t1.start();
Thread t2 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + ": " + "2");
LockSupport.unpark(t1);
},"t2");
t2.start();
}
}
2、交替打印abc
线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
2.1 wait notify 版
public class Test3 {
public static void main(String[] args) {
WaitNotify wt = new WaitNotify(1, 5);
new Thread(()->{
wt.print("a", 1, 2);
}).start();
new Thread(()->{
wt.print("b", 2, 3);
}).start();
new Thread(()->{
wt.print("c", 3, 1);
}).start();
}
}
/*
输出内容 等待标记 下一个标记
a 1 2
b 2 3
c 3 1
*/
class WaitNotify{
// 打印 a 1 2
public void print(String str, int waitFlag, int nextFlag){
for (int i = 0; i < loopNumber; i++) {
synchronized (this){
while(waitFlag != flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(str);
flag = nextFlag;
this.notifyAll();
}
}
}
private int flag; //等待标记 1
private int loopNumber; //循环次数
public WaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
}
3、交替打印奇偶数
两个线程交替打印0~100的奇偶数
● 偶线程:0
● 奇线程:1
● 偶线程:2
3.1、synchronized实现
缺点:效率低,会出现浪费的竞争。比如说,一直是偶数线程拿到锁,进行判断后不满足条件,就不会去执行打印语句,然后跳出,这样的话很多次竞争拿到锁是没有意义的。
import com.sun.javaws.IconUtil;
/*
两个线程交替打印0~100的奇偶数,用synchronized关键字实现
*/
public class WaitNotifyPrintOddEvenSyn {
private static int count;
private static final Object lock = new Object();
//新建两个线程
//1个只处理偶数,第二个只处理奇数(用位运算)
//用synchronized来通信
public static void main(String[] args) {
new Thread( ()->{
while(count < 100){
synchronized (lock){
if((count & 1) == 0){
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}, "偶数").start();
new Thread(()->{
while(count < 100){
synchronized (lock){
if((count & 1) == 1){
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}, "奇数").start();
}
}
3.2、wait/notify
优点:效率高,一个线程拿到锁就去打印,打印完,唤醒其他线程,自己就休眠,每次的竞争都是有意义的。
import javax.sound.sampled.FloatControl;
/*
两个线程交替打印0~100的奇偶数,用wait/notify实现
*/
public class WaitNotifyPrintOddEvenWait {
private static int count = 0;
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(new TurningRunner(), "偶数").start();
Thread.sleep(100);
new Thread(new TurningRunner(), "奇数").start();
}
// 1.拿到锁,我们就打印
// 2.打印完,唤醒其他线程,自己就休眠
static class TurningRunner implements Runnable{
@Override
public void run() {
while(count <= 100){
synchronized (lock){
// 拿到锁就打印
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notify();
if(count <= 100){
// 如果任务还没结束,就让出当期的锁,并休眠
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}