1. notify与wait
notify解锁线程,wait锁住线程。
使用notify和wait方法必须对线程进行同步,否则会报错。
notify和wait是依赖于同步锁,因此synchronized中的锁,必须填写notify和wait的线程。
接下来说说利用wait()和notify()来实现生产者和消费者并发问题:
1. 显然要保证生产者和消费者并发运行不出乱,主要要解决:当生产者线程的缓存区为满的时候,就应该调用wait()来停止生产者继续生产,而当生产者满的缓冲区被消费者消费掉一块时,则应该调用notify()唤醒生产者,通知他可以继续生产;同样,对于消费者,当消费者线程的缓存区为空的时候,就应该调用wait()停掉消费者线程继续消费,而当生产者又生产了一个时就应该调用notify()来唤醒消费者线程通知他可以继续消费了。
这里需要强调的是,wait方法和notify方法,并不是Thread线程上的方法,它们是Object上的方法。
因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。
wait()的意思
1. 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思
1. 通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor(锁)的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
1个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。
2. notify和wait的程序演示
public class Test {
// 在多线程间共享的对象上使用wait
private static String shareObj = "true";
public static void main(String[] args) throws IOException, ClassNotFoundException {
Thread1 a = new Thread1();
a.setName("a");
/* 启动线程a */
a.start();
Thread2 b = new Thread2();
b.setName("b");
/* 启动线程b */
b.start();
}
static class Thread1 extends Thread {
@Override
public void run() {
try {
synchronized (shareObj) {
System.out.println("开始等待线程获取锁");
/* 等待获取锁,需要其他线程将对象锁进行释放 */
shareObj.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName() + "线程,获取到锁");
}
}
static class Thread2 extends Thread {
@Override
public void run() {
try {
synchronized (shareObj) {
shareObj.notify();
System.out.println("线程" + Thread.currentThread().getName() + "调用shareObj.notify()");
/*
* 只要当前线程等待2s后,线程Thread1才会被执行 因为只要执行完synchronized语句块,对象锁才会被释放
* 当对象锁被释放的时候,线程Thread1才会执行,因为获得到锁
*/
sleep(2000);
}
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(this.getName() + "释放了锁");
}
}
}
3. 生产者与消费者的程序
package acc;
import java.util.Date;
public class Test {
final static Account ac = new Account();
/**
* 多线程并发问题演示 当多个线程同时访问一个对象时就可能发生同步问题 如果想看bug,不要使用虚拟机,使用真实电脑测试
*
* @param args
*/
public static void main(String[] args) {
ac.setN(1000);// 账户金额1000
final Date dt = new Date();
Thread xx = new Thread("张三") {
// 连续存款50次,每次100
@Override
public void run() {
try {
for (int i = 1; i <= 50; i++) {
System.out.println("");
/**
* 为代码段加锁,如果其他线程正在占用锁,这里就等待 此代码段开始之后就会占用这把锁,其他使用本锁的地方将会等待
*/
synchronized (Test.class) {
int n = ac.getN();// 获取余额
System.out.println(this.getName() + " - 查询余额 :" + n);
n = n + 100;
sleep(1);
ac.setN(n);
}
System.out.println(this.getName() + " - 存100查询 :" + ac.getN());
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.getName() + " -- OVER : " + ac.getN());
}
};
xx.start();
new Thread("李四") {
// 连续存款50次,每次100
@Override
public void run() {
try {
for (int i = 1; i <= 50; i++) {
System.out.println("");
/**
* 为代码段加锁,如果其他线程正在占用锁,这里就等待 此代码段开始之后就会占用这把锁,其他使用本锁的地方将会等待
*/
synchronized (Test.class) {
int n = ac.getN();// 获取余额
System.out.println(this.getName() + " - 查询余额 :" + n);
n = n - 100;
sleep(1);
ac.setN(n);
}
System.out.println(this.getName() + " - 取出100之后查询 :" + ac.getN());
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(this.getName() + " -- OVER : " + ac.getN());
}
}.start();
}
}