线程的通信
线程通信的例子:
涉及到的线程通信的三个方法:
- ①wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- ②notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,会唤醒优先级高的一个
- ③notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
说明: - 1,wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
- 2,wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常
- 3,wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中的(因为,任何一个类的对象都可以充当同步监视器,我们要用作为同步监视器的对象调用这三个方法,要保证任何一个对象都要有此方法,所以要在Object中定义)
package com.atguigu.java2;
/**
* 线程通信
*/
class Number implements Runnable{
private int number=1; //相当于共享数据
@Override
public void run() {
while (true){
synchronized (this) {
notify(); //唤醒一个,notifyAll唤醒全部。完整为this.notify()
if (number<=100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
//number增加之后,调用wait()方法,使得调用wait()方法的线程阻塞一下
try {
wait(); //完整为this.wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number=new Number();
Thread t1=new Thread(number);
Thread t2=new Thread(number);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
常问面试题:
sleep和wait的异同:
相同点:
一旦执行方法,都可以使当前的线程进入阻塞状态。
不同点:
①两个方法声明的位置不同:Thread类中声明静态的sleep()方法;Object类中声明wait();
②调用的范围不同:sleep()方法可以在任何需要的场景下调用;而wait()方法必须由同步监视器调用(即wait()方法必须使用在同步代码块或同步方法中);
③关于是否释放同步监视器的问题:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁(同步监视器),而wait()会释放锁。
线程通信经典问题:生产者/消费者问题
分析:
①是否为多线程问题?
是,生产者线程、消费者线程。
②是否有共享数据?
有,店员或产品数量。
③如何处理线程安全问题?
三种同步机制。
④是否涉及到线程通信
是。
package com.atguigu.java2;
/**
* 线程通信的应用
*/
class Clerk {
private int productCount = 0; //产品数量开始时为0
//增加产品数量(+)
public synchronized void produceProduct() { //同步方法,同步监视器为clerk。否则增加产品数后若阻塞可能会出现线程安全问题
if (productCount < 20){
productCount++; //先增加一个产品。否则会出现 “开始生产第0个产品”
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify(); //只要生产了一个产品便可以唤醒消费者消费
} else { //达到20,暂停生产
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//减少产品数量(-)
public synchronized void consumeProduct() {
if (productCount>0){ //产品数大于0才能减少
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
notify(); //只要消费了一个产品便可以唤醒生产者生产
}else{ //产品数量<=0。暂停消费
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{ //注意生产者是线程(此处用继承方式)
//生产者、消费者 *共用* Clerk,因此通过以下方式
private Clerk clerk;
public Producer(Clerk clerk) { //构造器
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName()+":开始生产产品。。。"); //继承方式可以省略Thread.currentThread.
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread { //消费者
//消费者处理方式和生产者一样
private Clerk clerk;
public Consumer(Clerk clerk) { //构造器
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始消费产品。。。"); //继承方式可以省略Thread.currentThread.
while (true) {
try {
Thread.sleep(20); //生产快,消费慢但有两个消费者
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk=new Clerk(); //clerk自始至终造了一个
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1=new Consumer(clerk);
c1.setName("消费者1");
Consumer c2=new Consumer(clerk);
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
同步方法
生产者握住同步监视器clerk之后进入同步方法produceProduct(),这时消费者就不能进入同步方法consumeProduct(),因为二者同步监视器均为this,即clerk。
只有生产出商品后,才有可能抢到同步监视器去消费。这样就安全了。(注意根据题意配合wait()、notify())