wait/notify基本概念
wait自动释放锁
当执行wait方法后,当前线程进入阻塞队列等待唤醒,唤醒后的线程进入就绪队列,等待cup分配资源。
当执行wait方法后,当前线程自动释放锁,进入阻塞队列。
package waitLetLockGo;
public class Service {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("start wait");
lock.wait();
//Thread.sleep(5000);//若将代码改成休眠,那么就成了同步了。
System.out.println("end wait");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package waitLetLockGo;
public class ThreadA extends Thread{
Object lock;
public ThreadA(Object lock) {
super();
this.lock=lock;
}
public void run() {
Service service=new Service();
service.testMethod(lock);
}
}
package waitLetLockGo;
public class ThreadB extends Thread{
Object lock;
public ThreadB(Object lock) {
super();
this.lock=lock;
}
public void run() {
Service service=new Service();
service.testMethod(lock);
}
}
package waitLetLockGo;
//线程A先获得锁,然后线程A进入阻塞队列,然后线程B获得锁,进入阻塞队列。
//都没有被唤醒
//结论:执行wait会释放锁。
public class Run {
public static void main(String[] args) {
Object lock=new Object();
ThreadA a=new ThreadA(lock);
a.start();
ThreadB b=new ThreadB(lock);
b.start();
}
}
控制台输出:
start wait
start wait
结论:执行wait会释放锁。
notify不释放锁:
package notifyNotLetLockGo;
public class Service {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait() ThreadName:"+Thread.currentThread().getName());
lock.wait();
System.out.println("end wait() ThreadName:"+Thread.currentThread().getName());
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
public void synNotifyMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("start notify ThreadName:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
lock.notify();
Thread.sleep(5000);
System.out.println("end notify ThreadName:"+Thread.currentThread().getName()+" time"+System.currentTimeMillis());
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
package notifyNotLetLockGo;
public class ThreadA extends Thread{
Object lock;
public ThreadA(Object lock) {
super();
this.lock=lock;
}
public void run() {
Service service=new Service();
service.testMethod(lock);
}
}
package notifyNotLetLockGo;
public class NotifyThread extends Thread{
Object lock;
public NotifyThread(Object lock) {
super();
this.lock=lock;
}
public void run() {
Service service=new Service();
service.synNotifyMethod(lock);
}
}
package notifyNotLetLockGo;
public class synNotifyMethodThread extends Thread{
Object lock;
public synNotifyMethodThread(Object lock) {
super();
this.lock=lock;
}
public void run() {
Service service=new Service();
service.synNotifyMethod(lock);
}
}
package notifyNotLetLockGo;
//实验目的:必须执行完notify方法所在的同步代码块后才释放锁。
public class Test {
public static void main(String[] args) {
Object lock=new Object();
ThreadA a=new ThreadA(lock);
a.start();
NotifyThread notifyThread=new NotifyThread(lock);
notifyThread.start();
synNotifyMethodThread c=new synNotifyMethodThread(lock);
c.start();
}
}
控制台输出:
begin wait() ThreadName:Thread-0
start notify ThreadName:Thread-1 time:1506777344320
end notify ThreadName:Thread-1 time1506777349321
end wait() ThreadName:Thread-0
start notify ThreadName:Thread-2 time:1506777349321
end notify ThreadName:Thread-2 time1506777354321
结论:必须执行完notify方法所在的同步synchronized代码块后才释放锁。
就像sleep(arg)方法一样,wait()方法也可以有参数,例如wait(2000),当线程调用后进入等待的状态,如果在2000毫秒内没有其他线程对其唤醒,那么等待的线程就会自动的唤醒。
package waitLong;
public class MyRunnable {
static private Object lock=new Object();
static private Runnable runnable=new Runnable() {
public void run(){
try {
synchronized (lock) {
System.out.println("begin wait time:"+System.currentTimeMillis());
lock.wait(5000);
System.out.println("end wait time:"+System.currentTimeMillis());
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
};
public static void main(String[] args) {
Thread t=new Thread(runnable);
t.start();
}
}
控制台输出:
begin wait time:1507688368045
end wait time:1507688373046
上面的例子中,在调用了wait(5000)后,并没有其他线程对其唤醒,但是我们看一下控制台可以发现,过了5s后线程自动退出等待的状态了。
当然,在等待的过程中也可以由其他线程对它唤醒。
package waitLong;
//提前唤醒
public class runnable {
static private Object lock=new Object();
static private Runnable runnable=new Runnable() {
public void run(){
try {
synchronized (lock) {
System.out.println("begin wait time:"+System.currentTimeMillis());
lock.wait(5000);
System.out.println("end wait time:"+System.currentTimeMillis());
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
};
static private Runnable runnable2=new Runnable() {
public void run() {
synchronized (lock) {
System.out.println("begin notify time:"+System.currentTimeMillis());
lock.notify();
System.out.println("end notify time:"+System.currentTimeMillis());
}
}
};
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(runnable);
t.start();
Thread.sleep(3000);
Thread t2=new Thread(runnable2);
t2.start();
}
}
控制台输出:
begin wait time:1507688633788
begin notify time:1507688636788
end notify time:1507688636788
end wait time:1507688636788
调用notify()方法一次只随机唤醒一个线程,如果想要唤醒多个线程就需要多次调用这个方法,当然你也可以选择调用notifyAll()(使用方法和notify方法相同)。
除了以上的一些用法,还需要注意的就是:
①避免notify过早,如果没有线程进入等待状态,这个notify通知等于未唤醒任何线程。同时当后续线程需要唤醒的时候却没有线程对它唤醒,容易造成无限等待。
解决方案:避免出现无wait状态下使用notify
②等待的条件发生变化,即:线程A等待取一个字符串,当线程B未完成将一个字符串放入一个队列中时候,线程A陷入等待,当线程B将一个字符串放入队列同时发出notifyAll通知,线程A被唤醒了,但是这个字符串却被同时在等待的线程C给截走了,所以线程A虽然被唤醒了,但它被唤醒的条件是无字符串可取,所以说是wait的条件改变了,伴随着的也就是IndexOutOfBoundsException错误。
解决方案:将wait条件控制语句if改为while。
以下就是一个错误的案例:
(如果修改,只需要将类Subtract中if(ValueObject.list.size()==0)更改为while(ValueObject.list.size()==0))
package waitOld;
public class Add {
private String lock;
public Add(String lock) {
super();
this.lock=lock;
}
public void add() {
synchronized (lock) {
ValueObject.list.add("anyString");
lock.notifyAll();
}
}
}
package waitOld;
public class Subtract {
private String lock;
public Subtract(String lock) {
super();
this.lock=lock;
}
public void subtract() {
try {
synchronized (lock) {
if(ValueObject.list.size()==0) {
System.out.println("wait begain thread name:"+Thread.currentThread().getName());
lock.wait();
System.out.println("wait end thread name:"+Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size="+ValueObject.list.size());
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
package waitOld;
public class ThreadAdd extends Thread{
private Add p;
public ThreadAdd(Add p) {
super();
this.p=p;
}
public void run() {
p.add();
}
}
package waitOld;
public class ThreadSubtract extends Thread{
private Subtract r;
public ThreadSubtract(Subtract r) {
super();
this.r=r;
}
public void run() {
r.subtract();
}
}
package waitOld;
import java.util.ArrayList;
import java.util.List;
public class ValueObject {
public static List list=new ArrayList();
}
package waitOld;
public class Run {
public static void main(String[] args) throws InterruptedException {
String lock=new String("");
Add add=new Add(lock);
Subtract subtract=new Subtract(lock);
ThreadSubtract subtract1thread=new ThreadSubtract(subtract);
subtract1thread.setName("subtract1thread");
subtract1thread.start();
ThreadSubtract subtract2thread=new ThreadSubtract(subtract);
subtract2thread.setName("subtract2thread");
subtract2thread.start();
Thread.sleep(1000);
ThreadAdd addthread=new ThreadAdd(add);
addthread.start();
}
}
生产者消费者模式
等待/通知最经典的案例就是“生产者消费者模式”了。
一生产一消费:操作值
package producterAndConsumer;
//生产者
public class P {
private String lock;
public P(String lock) {
super();
this.lock=lock;
}
public void setValue() {
try {
synchronized (lock) {
if(!ValueObject.value.equals("")) {
lock.wait();
}
String value=System.currentTimeMillis()+"_"+System.nanoTime();
System.out.println("set 的值是:"+value);
ValueObject.value=value;
lock.notify();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
package producterAndConsumer;
//消费者
public class C {
String lock;
public C(String lock) {
super();
this.lock=lock;
}
public void getValue() {
try {
synchronized (lock) {
if(ValueObject.value.equals("")) {
lock.wait();
}
System.out.println("get的值是:"+ValueObject.value);
ValueObject.value="";
lock.notify();
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
package producterAndConsumer;
//生产者线程
public class ThreadP extends Thread {
private P p;
public ThreadP(P p) {
super();
this.p=p;
}
public void run() {
while(true) {
p.setValue();
}
}
}
package producterAndConsumer;
//消费者线程
public class ThreadC extends Thread {
private C c;
public ThreadC(C c) {
super();
this.c=c;
}
public void run() {
while(true) {
c.getValue();
}
}
}
package producterAndConsumer;
//实体类,用于存放数据
public class ValueObject {
public static String value="";
}
package producterAndConsumer;
//测试代码
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();
}
}
以上是一生产者一消费者,在此基础上可以设计多生产者和多消费者。但是会陷入假死状态,即所有线程都进入等待状态。
为什么会出现这样的情况?因为一个生产者线程通知是随机的,可能唤醒生产者线程(生产检测时候发现value中已经有数据了,进入等待状态),也可能唤醒消费者(我们所期望的情况),但是随着程序不断的进行,所有的线程都会进入waiting(假死)状态。
修改方法:将notify()改为notifyAll(),不仅唤醒生产者线程同时也唤醒消费者线程。
上面的例子工具类使用的是包含一个字符串的对象。接下来的例子试讲生产者将数据放入list对象中:
一生产一消费:操作栈
package producterAndConsumer1;
import java.util.ArrayList;
import java.util.List;
//工具类
public class MyStack {
private List list=new ArrayList();
synchronized public void push() {
try {
if(list.size()==1) {
this.wait();
}
list.add("anyString="+Math.random());
this.notify();
System.out.println("push="+list.size());
}catch(InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String pop() {
String returnValue="";
try {
if(list.size()==0) {
System.out.println("pop操作中的"+Thread.currentThread().getName()+"线程呈wait状态");
this.wait();
}
returnValue=""+list.get(0);
list.remove(0);
this.notify();
System.out.println("pop="+list.size());
}catch(InterruptedException e) {
e.printStackTrace();
}
return returnValue;
}
}
package producterAndConsumer1;
//生产者
public class P {
private MyStack mystack;
public P(MyStack mystack) {
super();
this.mystack=mystack;
}
public void putService() {
mystack.push();
}
}
package producterAndConsumer1;
//消费者
public class C {
private MyStack mystack;
public C(MyStack mystack) {
super();
this.mystack=mystack;
}
public void popService() {
System.out.println("pop="+mystack.pop());
}
}
package producterAndConsumer1;
//生产者线程
public class ThreadP extends Thread{
private P p;
public ThreadP(P p) {
super();
this.p=p;
}
public void run() {
while(true) {
p.putService();
}
}
}
package producterAndConsumer1;
//消费者线程
public class ThreadC extends Thread{
private C c;
public ThreadC(C c) {
this.c=c;
}
public void run() {
while(true) {
c.popService();
}
}
}
package producterAndConsumer1;
//测试方法
public class run {
public static void main(String[] args) {
MyStack mystack=new MyStack();
P p=new P(mystack);
C c=new C(mystack);
ThreadP threadp=new ThreadP(p);
ThreadC threadc=new ThreadC(c);
threadp.start();
threadc.start();
}
}
控制台部分结果:
pop=anyString=0.10952348259646205
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.3117061801875739
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.5919274328346097
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.8462987957012925
pop操作中的Thread-1线程呈wait状态
push=1
pop=0
pop=anyString=0.1697638369373462
pop操作中的Thread-1线程呈wait状态
从以上的基础上改造为:一生产多消费,因为条件的更改(其他线程取走了数据,就是我们上面说过的:wait条件发生变化)导致一些线程发错误,同理我们需要将if条件控制更改为while。
若是改成多生产多消费的话:也会像出现像操作值那样假死,所以也要将notify()方法改为notifyAll()方法。