目录
一. 生产消费者模型分析
1. wait()
wait()方法会使线程停止运行,会释放对象锁。(状态变化:运行状态--->阻塞状态)
用于同步代码块或同步方法,并且必须是内建锁。
特点:
- wait()方法会使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列中,直到接到通知或被中断为止。
- wait()方法只能在同步方法或同步代码块中调用,如果调用wait()时没有适当的锁,会抛出异常。
- wait()方法执行后,当前线程释放锁,其他线程可以竞争该锁。
wait()(无参)之后的线程继续执行有两种方法:
- 调用该对象的notify()唤醒该线程。
- 线程等待时调用interrupt()中断该线程。
调用notify():调用notify()唤醒线程在讲述完notify()之后再举例说明。
调用interrupt():
package www.like.java;
class MyThread implements Runnable{
private Object obj = new Object();
@Override
public void run() {
synchronized (obj){
System.out.println("wait方法开始。。。");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束。。。");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
运行结果:产生中断异常,然后结束。
wait()还有一个有参构造:
参数timeout指的是等待的时间(精确到毫秒),如果到了预计时间还没有被唤醒,线程就结束等待,继续执行。
2. notify()
notify()方法会唤醒正在等待的线程,就是使停止的线程继续运行。
特点:
- notify()方法也必须在同步方法或同步代码块中调用,用来唤醒等待在该对象上的线程。如果有多个线程等待,就任意挑选一个线程唤醒。
- notify()方法执行后,唤醒线程不会立刻释放对象锁,要等待唤醒线程执行完毕后才释放对象锁。
package www.like.java;
class MyThread implements Runnable{
private Object obj = new Object();
private boolean flag;
public MyThread(Object obj, boolean flag) {
this.obj = obj;
this.flag = flag;
}
public void waitMethod(){
synchronized (obj){
System.out.println("wait方法开始。。。"+Thread.currentThread().getName());
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait方法结束。。。"+Thread.currentThread().getName());
}
}
public void notifyMethod(){
synchronized (obj){
System.out.println("notify方法开始。。。"+Thread.currentThread().getName());
obj.notify();
System.out.println("notify方法结束。。。"+Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag){
waitMethod();
}else {
notifyMethod();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread myThread1 = new MyThread(object,true);
MyThread myThread2 = new MyThread(object,false);
Thread waitThread = new Thread(myThread1,"等待线程");
Thread notifyThread = new Thread(myThread2,"唤醒线程");
waitThread.start();
Thread.sleep(1000);
notifyThread.start();
}
}
运行结果:
看一下结果,我们注意到wait()方法是在notify()方法结束之后才继续执行的,这也印证了notify()的一个特点,唤醒线程执行完毕之后才释放锁。只有等notify()方法释放锁后,wait()方法才能申请到锁,继续执行。如果wait()方法没有被唤醒,就一直处于等待状态中。
3. notifyAll()
顾名思义,就是唤醒所有在该对象上等待的线程。
4. 小结
4.1 线程阻塞的几种情况
状态变化:运行状态--->阻塞状态
- 调用sleep()方法,主动放弃占有的CPU,不会释放锁。
- 调用阻塞式I/O方法(read()、write()),在该方法返回前,线程阻塞。
- 线程试图获取一个同步监视器,但该同步监视器被其它线程所持有导致阻塞。
- 线程等待某个通知,即调用wait(),释放对象锁。
- 调用线程suspend(),将线程挂起,但该方法容易导致死锁,已被废弃。
4.2 锁对象的两个队列
每个锁对象都有两个队列,一个称为同步队列,一个称为等待队列。
同步队列中存放了因为竞争monitor失败导致阻塞的线程,这些线程等待CPU调度再次竞争锁。
等待队列中存放因为调用wait()方法导致线程等待的线程,唤醒后进入同步队列竞争锁。
二. 生产消费者模型实例
1. 调用notify()唤醒线程
package www.like.java;
//商品类
class Goods{
private String goodsName;
private int count;
//生产商品方法
public synchronized void set(String goodsName){
if(count > 0){
System.out.println("商品还有库存,等会在忙~");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.goodsName = goodsName;
this.count++;
System.out.println(Thread.currentThread().getName()+"生产"+toString());
//唤醒等待消费的线程(生产完商品后告诉消费者可以开始消费了)
notify();
}
//消费商品方法
public synchronized void get(){
if (count == 0){
System.out.println("商品卖完了,客官请等待~");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.count--;
System.out.println(Thread.currentThread().getName()+"消费"+toString());
//唤醒等待生产的线程(消费完商品后告诉生产者可以开始生产了)
notify();
}
@Override
public String toString() {
return "商品名称:"+goodsName+" 个数:"+count;
}
}
//消费者线程
class Consumer implements Runnable{
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.get();
}
}
//生产者线程
class Producer implements Runnable{
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
goods.set("笔记本");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
//一定要传同一个商品(对象),生产和消费是同一个产品
Goods goods = new Goods();
Thread producerThread = new Thread(new Producer(goods),"生产者");
Thread consumerThread = new Thread(new Consumer(goods),"消费者");
Thread.sleep(1000);
consumerThread.start();
producerThread.start();
}
}
2. 调用notifyAll()唤醒线程
package www.like.java;
import java.util.ArrayList;
import java.util.List;
//商品类
class Goods{
private String goodsName;
private int count;
//生产商品方法
public synchronized void set(String goodsName){
//对生产者不做限制,一直不停的生产
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.goodsName = goodsName;
this.count++;
System.out.println(Thread.currentThread().getName()+"生产"+toString());
//唤醒等待消费的线程
notifyAll();
}
//消费商品方法
public synchronized void get(){
//不断的执行判定条件
while(count == 0){
System.out.println("商品卖完了,客官请等等~");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.count--;
System.out.println(Thread.currentThread().getName()+"消费"+toString());
//唤醒生产者继续生产商品
notifyAll();
}
@Override
public String toString() {
return "商品名称:"+goodsName+" 个数:"+count;
}
public int getCount() {
return count;
}
}
//消费者线程
class Consumer implements Runnable{
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true){
//不断消费
goods.get();
}
}
}
//生产者线程
class Producer implements Runnable{
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (this.goods.getCount() < 10){
//不断生产
goods.set("笔记本");
}
}
}
public class Test {
public static void main(String[] args) {
Goods goods = new Goods();
//存储多个消费者、生产者线程
List<Thread> list = new ArrayList<>();
//10个消费者线程
for(int i = 0; i < 10; i++){
Thread thread = new Thread(new Consumer(goods),"消费者"+i);
list.add(thread);
}
//5个生产者线程
for(int i = 0; i < 5; i++){
Thread thread = new Thread(new Producer(goods),"生产者"+i);
list.add(thread);
}
for(Thread thread:list){
thread.start();
}
}
}