一、wait()与notify()
1.wait()-痴汉方法
前面介绍的join()对wait()进行了包装
wait()就是使线程停止运行,会释放对象锁。
特点:
1)wait()会使当前线程调用该方法后进行等待,并且将该线程置入锁对象的等待队列,直到接到通知或被中断为止。
2)wait()只能在同步方法或同步代码块中调用,如果调用wait()时没有适当的锁,会抛出异常。
3)wait()执行后,当前线程释放锁,其它线程可以竞争该锁。
wait()会使线程从运行态到阻塞态。何时回到运行态?调用notify()
wait()之后的线程继续执行有两种方法:
1)调用该对象的notify()唤醒该线程
2)线程等待时调用interrupt()中断该线程
wait(long time);如果到了预计时间还未被唤醒,线程将继续执行。time单位:毫秒
package com.xunpu.b;
class MyThread implements Runnable{
private Object object=new Object();
@Override
public void run() {
synchronized (object){
System.out.println("wait()开始");
try {
// object.wait();//会一直死等
object.wait(1000);//只等待1秒,1秒后继续执行。
} 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();
}
}
2.notify()
特点:
1)notify()也必须在同步方法或同步代码块中调用,用来唤醒等待在该对象上的线程。如果有多个线程等待,则任意挑选一个线程唤醒。
2)notify()执行后,唤醒线程不会立即释放对象锁,要等待唤醒线程全部执行完毕后才释放对象锁。
注意:notify()和wait()只有执行同一个对象时,才会释放资源。
package com.xunpu.b;
class MyThread implements Runnable{
//锁定对象
private Object object=new Object();
private boolean flag;//设置标志来决定走哪个方法
public MyThread(Object object, boolean flag) {
this.object = object;
this.flag = flag;
}
public void waitMethod(){
synchronized (object){
System.out.println("wait()开始"+Thread.currentThread().getName());
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait()结束"+Thread.currentThread().getName());
}
}
public void notifyMethod(){
synchronized (object){
System.out.println("notify()开始。。。"+Thread.currentThread().getName());
object.notify();
System.out.println("notify()结束。。。"+Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag==true){
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();
}
}
3.notifyAll()
唤醒所有在该对象上等待的线程。
package com.xunpu.b;
class MyThread implements Runnable{
//锁定对象
private Object object=new Object();
private boolean flag;//设置标志来决定走哪个方法
public MyThread(Object object, boolean flag) {
this.object = object;
this.flag = flag;
}
public void waitMethod(){
synchronized (object){
System.out.println("wait()开始"+Thread.currentThread().getName());
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait()结束"+Thread.currentThread().getName());
}
}
public void notifyMethod(){
synchronized (object){
System.out.println("notify()开始。。。"+Thread.currentThread().getName());
object.notifyAll();
System.out.println("notify()结束。。。"+Thread.currentThread().getName());
}
}
@Override
public void run() {
if(flag==true){
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);//同一个对象,才可以释放。
for(int i=0;i<10;i++){
Thread threadi=new Thread(myThread1,"waitthread"+i);
threadi.start();
}
Thread waitThread=new Thread(myThread1,"等待线程");
Thread notifyThread=new Thread(myThread2,"唤醒线程");
waitThread.start();//先启动等待线程!!
Thread.sleep(1000);//保证等待线程先启动!!
notifyThread.start();
}
}
4.线程阻塞的五种情形
1)调用sleep(),主动放弃占有的CPU,不会释放对象锁。
2)调用阻塞式IO方法(read()、write()),在该方法返回前,线程阻塞。
3)线程试图获取一个monitor,但该monitor被其它线程所持有,导致阻塞。
4)线程等待某个通知,即调用wait(),释放对象锁。
5)调用suspend(),将线程挂起,容易导致死锁,已被废弃。
5.monitor的两个队列
每个monitor都有两个队列,一个称为同步队列,一个称为等待队列。
同步队列中都存放了因为竞争monitor失败导致阻塞的线程,这些线程等待CPU调度再次竞争锁。
等待队列存放因为调用wait()导致线程等待的线程,唤醒后进入同步队列竞争锁。
同步队列:所有获取Monitor失败的线程进入同步队列,等待获取锁。
等待队列:调用了wait()阻塞的线程进入等待队列。
二、生产消费者模型
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
package com.xunpu.b;
/**
* 单线程版本的生产者-消费者模型
*/
class Goods{
private String goodsName;
private int count;
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", count=" + 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()+"生产"+goodsName+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()+"消费"+goodsName+toString());
notify();//唤醒生产者继续生产线程
}
}
//消费者线程
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 produceThread=new Thread(new Producer(goods),"生产者线程");
Thread consumerThread=new Thread(new Consumer(goods),"消费者线程");
consumerThread.start();
Thread.sleep(1000);
produceThread.start();
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 多线程版本的生产者-消费者模型
*/
class Goods{
private String goodsName;
private int count;
public int getCount() {
return count;
}
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", count=" + count +
'}';
}
//生产商品方法
public synchronized void set(String goodsName){
// //不断判断条件
// while(count>0){
// System.out.println("商品还有库存,等会再生产!");
// try {
// wait();//等待消费者消费
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
try {
Thread.sleep(20);//每秒生产一个商品
} catch (InterruptedException e) {
e.printStackTrace();
}
this.goodsName=goodsName;
this.count++;
System.out.println(Thread.currentThread().getName()+"生产"+goodsName+toString());
notifyAll();//唤醒等待消费的线程
}
//消费商品方法
public synchronized void get(){
//为什么是while? 如果消费者1来消费,此时没有库存,然后回唤醒生产者生产商品,生产一个刚好被消费。在生产的时候,消费者2
//也来消费,此时可能会出现count<0的情况,一个商品被消费了多次。
//不断判断执行条件
while(count==0){
System.out.println("商品卖完了,请您等会。。。");
try {
wait();//等待商品生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.count--;
System.out.println(Thread.currentThread().getName()+"消费"+goodsName+toString());
notifyAll();//唤醒生产者继续生产线程
}
}
//消费者线程
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(true){
// goods.set("炒米饭");//生产
// }
while(this.goods.getCount()<200){
this.goods.set("炒米饭");
}
}
}
public class Test2 {
public static void main(String[] args) {
Goods goods=new Goods();
//存储多个生产-消费者线程
List<Thread> list=new ArrayList<Thread>();
//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();
}
}
}
三、wait()、notify()、notifyAll()的练习
package com.xunpu.c;
/**
* 线程练习题1:写两个线程,一个线程打印1~52,另一个线程打印A~Z。
* 打印顺序是12A34B.....5152Z
* 思路:
* 观察,每打印两个数字,就打印一个字母;打印完字母后立即打印数字,重复循环,直到打印完。
* 线程间通信:信号量
* 设置信号量控制打印字母还是数字
*/
class Print{
int flag=1;
int count=1;
char c='A';
public synchronized void printNum(){
while(flag!=1){
//此时应该打印字母
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(2*count-1);
System.out.print(2*count);
flag=2;
notify();
}
//打印字母
public synchronized void printChar(){
while(flag!=2){
//打印数字
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print((char)(count-1+'A'));
//继续前行
count++;
flag=1;
notify();
}
}
public class Demo1 {
public static void main(String[] args) {
final Print print=new Print();
// while(print.count!=27) {
// print.printNum();
// print.printChar();
// }
new Thread(()->{
for(int i=0;i<26;i++){
print.printNum();
}
}).start();
new Thread(()->{
for(int i=0;i<26;i++){
print.printChar();
}
}).start();
}
}
package com.xunpu.c;
/**
* 编写一个程序,启动三个线程,三个线程的名称分别是A,B,C;每个线程将自己的名称在屏幕上打印5遍。
* 打印顺序是ABCABCABC......
*/
class MyPrint{
int flag=1;
static int count=0;//控制打印次数
public synchronized void printA() {
while(flag!=1){
//此时说明正在打印其它线程,等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
count++;
flag=2;//告诉B该B打印了。
notifyAll();//防止notify()任意一个线程,将printC()唤醒,不能打印B,避免死锁。
}
public synchronized void printB(){
while (flag!=2){
//说明此时正在打印其它线程,等待。
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
count++;
flag=3;//告诉C,该C打印了。
notifyAll();
}
public synchronized void printC(){
while(flag!=3){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
count++;
flag=1;
notifyAll();
}
}
class MyThread implements Runnable{
MyPrint myPrint=new MyPrint();
public MyThread(MyPrint myPrint) {
this.myPrint = myPrint;
}
@Override
public void run() {
while(myPrint.count<16) {
if (Thread.currentThread().getName().equals("A")) {
myPrint.printA();
} else if (Thread.currentThread().getName().equals("B")) {
myPrint.printB();
} else {
myPrint.printC();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
MyPrint myPrint=new MyPrint();
MyThread myThread=new MyThread(myPrint);
Thread thread1=new Thread(myThread,"A");
Thread thread2=new Thread(myThread,"B");
Thread thread3=new Thread(myThread,"C");
thread1.start();
thread2.start();
thread3.start();
}
}