java中的wait、notify、notifyAll、join、sleep
java中的wait、notify、notifyAll、join
Java虚拟机上运行的每个对象来说都有一个内置的监视器(Monitor),Monitor里面又有一个内置对象和两个池,锁(monitor)池和等待(wait)池(等待队列),而这两个池又与Object基类的wait、notify、notifyAll三个方法和同步代码块相关。
锁池的本质
就是假设线程 A 已经拥有了某个对象(不是类)的锁,而其它线程 B、C 想要调用这个对象的某个 synchronized 方法(或者块),由于这些 B、C 线程在进入对象的 synchronized 方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程 A 所拥有,所以这些 B、C 线程就进入了该对象的锁池,这就是锁池。
等待池的本质
就是假设线程 A 调用了某个对象的 wait() 方法,线程 A 就会释放该对象的锁(因为 wait() 方法必须在 synchronized中使用,所以执行 wait() 方法前线程 A 已经持有了该对象的锁),同时线程 A 就进入到了该对象的等待池中。
如果此时线程 B 调用了相同对象的 notifyAll() 方法,则处于该对象等待池中的线程就会全部进入该对象的锁池中去准备争夺一个锁的拥有权,没有获取到锁而已经呆在锁池的线程只能继续等待其他机会获取锁,而不能再主动回到等待池(除非该线程调用wait方法)。而如果此时线程 B 调用的是相同对象的 notify() 方法,则仅仅会有一个处于该对象等待池中的线程(随机,具体唤醒哪一个,由cpu调度决定)进入该对象的锁池中去准备争夺锁的拥有权。
工作流程
wait的工作流程:调用任何对象的wait()方法会让当前线程进入等待池,并且用来释放对象锁
对象调用notify方法,用来通知那些可能等待该对象的对象锁的其他线程(就是在等待池里面的线程们)。如果调用notify时,有多个线程等待,则线程规划器任意从等待池挑选出其中一个的线程来发出通知,并使它等待获取该对象的对象锁(调用notify 后,当前线程不会马上释放该对象锁,此时内定的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,内定的线程才可以获取该对象锁),但不惊动其他同样在等待池的线程们。当第一个获得了该对象锁的线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他在锁池等待的线程由于没有得到该对象的通知,会继续阻塞在锁池,直到这个对象发出一个 notify 或 notifyAll。
例子:
package waitTest;
public class CountThread extends Thread {
Object lockObj;
String threadName;
public CountThread() {
}
public CountThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj=lockObj;
}
public void run() {
int i = 1;
synchronized (lockObj) {
System.out.println(getName() + " : " + i);
try {
lockObj.wait();
System.out.println(getName()+":do something");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class NotifyThread extends Thread{
Object lockObj;
public NotifyThread(Object lockObj) {this.lockObj=lockObj;}
public void run() {
try {
sleep(1000);
synchronized(lockObj) {
System.out.println("notifyAll方法执行完毕");
lockObj.notifyAll();
sleep(5000);
System.out.println("还没退出notify的synchronized的代码块");
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
public class NotifyTest2 {
public static void main(String[] args) {
Object lockObj=new Object();
CountThread countThread1=new CountThread("1号计数器",lockObj);
countThread1.start();
new NotifyThread(lockObj).start();
}
}
执行结果
注意:notify之后并不会马上释放对象锁,而是等notify的synchronized的代码块执行完毕后才释放对象锁。此时在同步队列的线程竞争锁。
wait和sleep的差别
sleep方法属于Thread类中的,而wait方法则属于Object类中的。
调用sleep方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,线程不会释放对象锁。调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
一般画的线程的状态大概如下:
此图画的有问题,从waiting队列被notify唤醒后之后应该先进入blocked队列争对象锁的使用,得到对象锁之后进入runable状态,从上面的举的例子也可以看出应该是该过程。一般blocked队列用AQS来进行实现。
AQS可以参考另一篇博文:https://www.cnblogs.com/kexinxin/p/11679064.html
join方法
join方法属于Thread类中的方法,
Thread类中的join方法主要作用就是同步,它可以使线程之间的并行执行变为串行执行
join方法的作用:在A线程中调用B线程的join方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
join方法的实现原理
join方法的原理就是调用相应线程的wait方法进行等待操作的,例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。
综合例子
利用wait,notify,notifyAll实现并行执行多任务
package waitTest;
public class CountThread extends Thread {
Object lockObj;
String threadName;
public CountThread() {
}
public CountThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj=lockObj;
}
public void run() {
int i = 1;
synchronized (lockObj) {
System.out.println(getName() + " : " + i);
try {
lockObj.wait();
System.out.println(getName()+":do something");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package waitTest;
public class NotifyThread extends Thread{
Object lockObj;
public NotifyThread(Object lockObj) {this.lockObj=lockObj;}
public void run() {
try {
sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
synchronized(lockObj) {
System.out.println("notifyAll方法执行完毕");
lockObj.notifyAll();
}
}
}
package waitTest;
public class NotifyTest {
Object lockObj;
public static void main(String[] args) throws InterruptedException {
Object lockObj=new Object();
CountThread countThread1=new CountThread("1号计数器",lockObj);
CountThread countThread2=new CountThread("2号计数器",lockObj);
CountThread countThread3=new CountThread("3号计数器",lockObj);
countThread1.start();
countThread2.start();
countThread3.start();
new NotifyThread(lockObj).start();
countThread1.join();
countThread2.join();
countThread3.join();
System.out.println("kexinxin");
}
}