wait和notify方法可以实现线程通信,模型类似于操作系统中的信号量。信号量其实就是一个变量,有一个等待的队列。java中任何对象都可以当做前面所谓的“信号量”,或者说,在java的模型中,是一个锁,任何对象都可以是一个锁。那么如何使用wait和notify呢?
假设现在有一段代码,这段代码可以被多个线程运行,如果检测到某一个条件成立,那么就需要将当前的线程等待,也就是wait。这里的wait通常是针对某一种资源或者一种条件。那么就可以用一个锁来标识具体某一种资源或者条件。锁内部就会维护这类资源的等待的线程,到时候唤醒就知道唤醒哪些线程了。所以说,wait和nofity必须依附于一个锁,也就是区分不用等待队列。也就是,wait和nofity必须是某一个对象的同步方法或者块。
下面是一个例子。
package ThreadTest;
public class Case1 {
public static void main(String args[]){
final String lock = "abc";
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() + " starts...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t1.setName("t1");
Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() + " starts...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t3.setName("t3");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
//System.out.println(Thread.currentThread().getName() + " starts...");
//lock.notify();
//System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t2.setName("t2");
t1.start();
t2.start();
t3.start();
}
}
一个string对象是一个锁,可以代表某一种资源,有三个线程,t1和t3都需要wait,t2可以notify,如果把t2注释,那么会:
可以看到会被阻塞。如果t2取消注释:
可以看到其中的t1被唤醒,继续执行结束。
当一个线程执行到wait的同步区时,可定是得到了锁,当执行完以后,它会释放对象的锁,这样其他的线程可以得到锁,否则notify的线程也无法进入同步区。nofity方法会随机唤醒一个等待的线程,然后也会释放锁。那么wait和notify是什么时候释放的锁?
wait是刚执行完wait方法以后就释放锁,不会等待wait后面的同步区里面的方法的执行。而notify则是等待同步区的全部方法执行完才释放。
package ThreadTest;
public class Case2 {
public static void main(String args[]){
final String lock = "abc";
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() + " starts...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t1.setName("t1");
Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() + " starts...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t3.setName("t3");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
//lock.notify();
}
}
});
t2.setName("t2");
t1.start();
//t2.start();
t3.start();
}
}
只有wait的两个线程执行,结果:
可以看到,如果是等到wait的同步块执行完才能释放锁,那么t1的end语句也应该被打印出来。而结果则是t3的starts先打印,说明wait执行完以后立刻释放锁,后面的代码也不会执行。
但是notify则是执行完同步块才释放的。
package ThreadTest;
public class Case2 {
public static void main(String args[]){
final String lock = "abc";
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() + " starts...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t1.setName("t1");
Thread t3 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() +" starts...");
lock.notify();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t3.setName("notify3");
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() +" starts...");
lock.notify();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" ends...");
}
}
});
t2.setName("notify2");
t1.start();
t2.start();
t3.start();
}
}
有两个notify的线程,每一个都会sleep一段时间,如果是执行完notify立刻释放,那么另一个线程是有机会执行的。但是结果:
说明,每一个notify的块执行完才轮到另一个notify执行,证明执行完块才释放的锁。
下面用wait notify机制实现一个生产者消费者问题。
package gggg;
import java.util.LinkedList;
import java.util.Queue;
class Pool {
Queue<Integer> queue = new LinkedList<>();
int size = 5;
public Pool() {
}
public Pool(int size) {
this.size = size;
}
}
class P {
Pool pool;
public P(Pool pool) {
this.pool = pool;
}
public void produce() {
synchronized (pool) {
System.out.println(Thread.currentThread().getName() + " wants to produces");
if (pool.size <= pool.queue.size())
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " produces");
pool.queue.add(1);
System.out.println(Thread.currentThread().getName() + " size is " + pool.queue.size());
pool.notifyAll();
}
}
}
class C {
Pool pool;
public C(Pool pool) {
this.pool = pool;
}
public void consume() {
synchronized (pool) {
System.out.println(Thread.currentThread().getName() + " wants to consume");
if (pool.queue.size() <= 0)
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.queue.poll();
System.out.println(Thread.currentThread().getName() + " consumes");
pool.notify();
}
}
}
public class Case4 {
public static void main(String[] args) {
Pool pool = new Pool();
P p = new P(pool);
C c = new C(pool);
Thread tps = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ;i < 100; i++){
p.produce();
}
}
});
Thread tcs = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ;i < 100; i++){
c.consume();
}
}
});
tcs.start();
tps.start();
}
}
有一个缓存池,里面是一个队列,有存储上限值。为了简化,只有两个线程,一个生产一个消费。因此可以用pool做为锁,让wait操作和nofity以pool这个对象为锁。因为当生产者运行时,如果要唤醒,那么只可能唤醒消费者。
那如果要有多个线程呢?比如多个生产者线程和多个消费者线程。有两点需要改。第一个是这时就不能调用notify了,因为要唤醒多个生产者或者消费者,那么就只能使用notifyAll了。第二,如果现在是生产者要唤醒过个消费者,如果直接notify,不是也会唤醒其他的消费者么?确实有这样的问题。解决办法是把原本的if改为while,这样唤醒以后还要检测一次条件,不符而就重新wait,这就是多线程中该问题的设计,很巧妙。
package awge;
import java.util.LinkedList;
import java.util.Queue;
class Pool {
Queue<Integer> queue = new LinkedList<>();
int size = 5;
public Pool() {
}
public Pool(int size) {
this.size = size;
}
}
class P {
Pool pool;
public P(Pool pool) {
this.pool = pool;
}
public void produce() {
synchronized (pool) {
System.out.println(Thread.currentThread().getName() + " wants to produces");
while (pool.size <= pool.queue.size())
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " produces");
pool.queue.add(1);
System.out.println(Thread.currentThread().getName() + " size is " + pool.queue.size());
pool.notifyAll();
}
}
}
class C {
Pool pool;
public C(Pool pool) {
this.pool = pool;
}
public void consume() {
synchronized (pool) {
System.out.println(Thread.currentThread().getName() + " wants to consume");
while (pool.queue.size() <= 0)
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.queue.poll();
System.out.println(Thread.currentThread().getName() + " consumes");
pool.notifyAll();
}
}
}
public class Case4 {
public static void main(String[] args) {
Pool pool = new Pool();
P p = new P(pool);
C c = new C(pool);
Thread[] tps = new Thread[100];
Thread[] tcs = new Thread[100];
for (int i = 0; i < tps.length; i++) {
tps[i] = new Thread(new Runnable() {
@Override
public void run() {
p.produce();
}
});
tps[i].setName("p" + i);
tcs[i] = new Thread(new Runnable() {
@Override
public void run() {
c.consume();
}
});
tcs[i].setName("c" + i);
}
for (int i = 0; i < tps.length; i++) {
tcs[i].start();
}
for (int i = 0; i < tps.length; i++) {
tps[i].start();
}
}
}