3.1.1不使用等待/通知机制实现线程间通信
3.1.2什么是等待/通知机制
两个线程完全是主动式地读取一个变量,在花费读取时间的基础上,读到的数据并不确定是否是想要的,因此需要“等待通知”机制
3.1.3等待/通知机制的实现
1wait()方法:作用是使当前执行代码的线程进行等待,该方法是Object类的方法,用来将当前线程置入“预执行队列”中,并在wait()所在代码行处停止执行,直到接到通知或被中断为止。在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果在调用wait()时没有持有适当的锁,则会抛出异常,而不是try/catch捕获异常,因为该异常是RuntimeException的一个子类
2notify()方法:也要在同步方法或同步代码块中调用,如果调用notify()时没有持有适当的锁,也会抛出和wait()一样的异常。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。
在执行notify方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁
总结:两个方法都要获得该对象的对象级别锁才能调用,wait()执行后,当前线程立即释放该对象的对象锁;notify()执行后,当前线程要等到同步代码块执行完毕后才能释放锁,并唤醒其他线程
这两个方法是Object类型的方法,在进行等待和通知时都是由锁对象自己来调用方法的
wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒
notify()方法可以随机唤醒等待队列中等待同一共享资源的一个线程,使线程从等待状态进入运行状态
notifyAll()方法可以是所有正在等待队列中等待同一共享资源的“全部线程”从等待状态退出,进入运行状态
线程的四种状态
1)新创建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是 一个准备运行的状态。如果线程抢占到了CPU资源,此线程就处于Running(运行)状态
2)Runnable,Running可以互相切换,因为有可能线程一段时间后,有其他高优先级的线程抢占到了CPU资源
线程进入Runnable状态的五种情况:
调用sleep()方法后经过的时间超过了指定的休眠时间
线程调用的阻塞IO已经返回,阻塞方法执行完毕
线程成功地获得了试图同步的监视器
线程正在等待某个通知,其他线程发出了通知
处于挂起状态的线程调用了resume恢复方法
3)Blocked阻塞状态:如果遇到了一个IO操作,而CPU此时处于空闲状态,可能会转而把CPU分给其他线程,这是称为暂停状态
出现阻塞状态的5种情况:
线程调用sleep方法,主动放弃占用处理资源
线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞
线程试图获取一个同步监视器,但该监视器正被其他线程所持有
线程等待某个通知
程序调用了suspend方法将该线程挂起,此方法容易出现死锁,避免使用
4)run方法运行结束后进入销毁阶段,整个线程执行完毕
每个锁对象都有两个两个队列:一个是就绪队列,一个是阻塞队列
3.1.4方法wait锁释放与notify锁不释放
当方法wait被执行后,锁被自动释放,但执行完notify方法,锁不自动释放,要等到线程执行完同步代码块后释放锁
package three.fourth.test;
import three.fourth.extthread.ThreadA;
public class Test2 {
public static void main(String[] args) {
// 本例用来说明呈wait状态的线程调用interrupt方法时会出现异常
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.interrupt();
}
}
- 执行完同步代码块就会释放对象的锁
- 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
- 在执行同步代码块的过程中,执行力锁所属对象的wait方法,这个线程会释放对象锁,而次线程对象会进入线程等待池中,等待被唤醒
3.1.6只通知一个线程
调用方法notify一次只通知一个线程进行唤醒
当多次调用notify方法时,会随机将等待wait状态的线程进行唤醒
3.1.7唤醒所有线程
当notify方法的调用次数小于线程对象的数量,会出现有部分线程对象对象无法被唤醒的情况,可以使用notifyAll方法唤醒全部线程
3.1.8方法wait(long)的使用
该方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒
3.1.9通知过早
如果通知过早,会打断程序的正常逻辑
为了防止通知过早的情况,可以设置一个布尔类型的标志位
3.1.10等待wait的条件发生变化
此处体现出if和while的区别,if只能判断一次,而while可以循环判断,可以避免覆盖
3.1.11生产者/消费者模式实现
一生产与一消费:操作值
创建生产者类:
package productandclient;
public class P {
private String lock;
public P(String lock) {
super();
this.lock = lock;
}
public void setValue() {
synchronized (lock) {
if(!valueObject.value.equals("")) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
String value = System.currentTimeMillis()+"_"+System.nanoTime();
System.out.println("set的值是"+value);
valueObject.value = value;
lock.notify();
}
}
}
创建消费者类:
package productandclient;
public class C {
private String lock;
public C(String lock) {
super();
this.lock = lock;
}
public void getValue() {
synchronized (lock) {
if(valueObject.value.equals("")) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("get的值为"+valueObject.value);
valueObject.value = "";
lock.notify();
}
}
}
创建生产者线程和消费者线程:
package productandclient;
public class ThreadP extends Thread {
private P p;
public ThreadP(P p) {
super();
this.p = p;
}
public void run() {
while(true) {
p.setValue();
}
}
}
package productandclient;
public class ThreadC extends Thread {
private C c;
public ThreadC(C c) {
super();
this.c = c;
}
public void run() {
while(true) {
c.getValue();
}
}
}
运行测试类:
package productandclient;
public class Run {
public static void main(String[] args) {
String lock = new String("");
P p = new P(lock);
C c = new C(lock);
ThreadP threadp = new ThreadP(p);
ThreadC threadc = new ThreadC(c);
threadp.start();
threadc.start();
}
}
运行结果如图所示:
可以看出set和get方法是交替执行的,但是谁先执行是随机的,看谁先抢到线程的执行权
多生产与多消费:操作值-假死
“假死”现象就是线程进入了waiting等待状态,如果全部线程都进入了waiting状态,则程序就不再执行任何业务功能了,整个项目呈停止状态
由于在这种模式下生产者可能会唤醒生产者,消费者可能会唤醒消费者,这样积累下去,最后导致所有线程都呈waiting状态,程序最终呈现假死状态
解决方法:将notify方法换成notifyAll方法,不光唤醒同类线程,也唤醒异类线程
解决wait条件改变:if换成while