一、wait()
最特殊的wait()方法,为什么把它单独拉出来,因为wait()是Object类的native方法,也就是每一个对象都有的方法,跟线程没有直接关系
使用场景往往是:
public class JoinVsWaitExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个用于线程同步的对象
final Object lock = new Object();
// 创建另一个线程
Thread waitThread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("waitThread 进入同步块,准备调用 wait()");
// 调用 wait() 方法,线程释放锁并进入等待状态
lock.wait();
System.out.println("waitThread 被唤醒,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitThread.start();
// 主线程休眠 1 秒,确保 waitThread 进入等待状态
Thread.sleep(1000);
// 主线程获取锁并调用 notify() 方法唤醒等待的线程
synchronized (lock) {
System.out.println("主线程调用 notify() 唤醒 waitThread");
lock.notify();
}
System.out.println("主线程继续执行,不等待 waitThread 完成");
}
}
解释:
首先有一个前提条件:lock是一个Java对象,对象头中有mark word字段来记录锁的持有状态,同时lock也关联了一个监视器对象(Monitor)
1. 从synchronized(lock)开始,waitThread线程就占有了lock对象的Monitor锁(lock的对象头用mark word字段记录了是waitThread在占有锁)
2. 当运行到lock.wait()时,waitThread线程释放了lock的监视器锁,lock关联的监视器中有一个waitSet来收集等待使用lock监视器锁的线程,此时waitThread就加入了waitset()
3. 此时其他线程(本例中为主线程)可以用 synchronized (lock) 获取lock的监视器锁,mark word也会同步更新:lock被主线程占用中
4. 主线程调用lock.notify(),从waitSet中随机选一个线程唤醒,waitSet中目前只有waitThread,因此waitThread线程重新获得lock的锁,可以继续进行
你有没有发现一个问题,既然同一时间只有一个线程可以获取对象的锁,那notifyAll()方法有什么用呢?全部唤醒也只能选其中1个获得锁,跟notify()功能不是一样的吗?
答:对象头中有两个池:entryList锁池和waitSet等待池,线程调用wait()后进入的是waitSet,不竞争锁,主线程调用notify()后,会从waitSet中随机选一个进入entryList锁池,进入后才开始竞争锁。而notifyAll()是唤醒所有waitSet中的线程进入entryList。
也就是说唤醒不代表让等待的线程获得锁,而是仅仅给它一个竞争锁的机会
这里又能引出一个知识点:
进入waitSet的线程是wait或timewait状态,进入entryList的才是阻塞态
二、join()、sleep()、yeild()
2.1 join()
用法:
public class JoinVsWaitExample {
public static void main(String[] args) throws InterruptedException {
Thread joinThread = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("joinThread 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动线程
joinThread.start();
// 使用 join() 方法,主线程会等待 joinThread 执行完毕
System.out.println("主线程调用 join() 等待 joinThread");
joinThread.join();
System.out.println("主线程继续执行,因为 joinThread 已完成");
}
}
解释:
1. joinThread线程被创建,之后一直在运行(sleep足足2秒钟,按常理主线程应该早运行完了)
2. 主线程调用join(),实现等待joinThread运行
3. joinThread线程sleep完了,也打印完了,自我销毁
4. 主线程发现joinThread不存在了,正常运行下去,打印并结束
看下join()的源码:
join的实现依靠 while循环+wait(0) ,这样主线程只要发现joinThread还存活,就释放joinThread对象的锁,进入joinThread对象的waitSet,状态变成wait(),CPU就不会再运行主线程因此join()和wait()的区别在:
wait()是释放了业务对象的锁,以此来给别的线程用。
join()是释放了子线程的锁,以此让自己阻塞,等待子线程运行完。而没有释放业务对象的锁
因此说join没有释放锁,指的是没有释放业务对象的锁
2.2 sleep()
sleep是最容易理解的,它只表示什么都不做,不释放锁,线程进timewait,是Thread的静态方法
因此与线程通信没任何关系,因为它不释放锁啊
说说它怎么中断(注意它不能被唤醒,)
2.3 yield()
yield()表示愿意让出CPU资源,Thread的静态方法
不释放任何锁,只是给JVM一个信号,可以把CPU多分配给其他线程
与线程并发编程也没什么直接关系,不能实现线程通信