【学习笔记】线程(八)之线程通信

线程(八)之线程通信

 

生产者消费者问题

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖,互为条件

  • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费

  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费

  • 在生产者消费者问题中,仅有synchronized 是不够的

    • synchronized 可阻止并发更新同一共享资源,实现了同步

    • synchronized 不能用来实现不同线程之间的消息传递(通信)

 

在Object 类中有两个关于线程通信的方法:

wait() //表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁

wait(long timeout) //指定等待的毫秒数

notify() //唤醒一个处于等待状态的线程

notifyAll() //唤醒同一个对象上所有调用wait() 方法的线程,优先级别高的线程优先调度

注意:这两个方法,都只能在同步方法或同步代码块中使用,否则会抛出异常IIIegaIMonitorStateException

 

解决方式1:管程法

并发协作模型”生产者/消费者模式“--->管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)

  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)

  • 缓冲区:消费者不能直接使用生产者的数据,它们之间有个”缓冲区“

生产者将生产好的数据放入缓冲区,消费者从缓冲区中拿出数据

 

package com.thread.cooperate;
​
public class TestPC {
    public static void main(String[] args) {
       SynContainer synContainer = new SynContainer();
       new Productor(synContainer).start();
       new Consumer(synContainer).start();
    }
}
​
//产品
class Chicken{
    int id;
​
    public Chicken(int id) {
        this.id = id;
    }
}
​
//生产者
class Productor extends Thread{
    SynContainer synContainer;
​
    public Productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
​
            synContainer.push(new Chicken(i));
            System.out.println("生产了" + i +"只鸡");
        }
    }
}
//消费者
class Consumer extends Thread{
    SynContainer synContainer;
​
    public Consumer(SynContainer synContainer) {
        this.synContainer = synContainer;
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("消费了"+synContainer.pop().id+"只鸡" );
        }
    }
}
//缓冲区
class SynContainer{
    Chicken[] chickens = new Chicken[10];
    int count = 0;
​
    //生产者放入产品
    public synchronized void push(Chicken chicken)  {
        //如果容器满了,需要生产者等待
        while (count == chickens.length){
            try {
                this.wait();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
        }
​
            //如果没有满,需要生产者放入产品
            chickens[count] = chicken;
            count++;
            //通知消费者来消费
            this.notifyAll();
    }
    //消费者消费产品
    public synchronized  Chicken pop(){
        //等待生产者生产
        while(count <= 0){
            try {
                this.wait();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //消费
        count--;
        Chicken chicken = chickens[count];
        //通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

 

在这个案例中,生产者和消费者的运行速度不同,所以可能会出现生产者还没有打印出生产第十只鸡,然而消费者却已经打印出消费了第十只鸡的情况

image-20220802182846393

还有一种情况:消费者已经消费了第9只鸡,却没有打印,而生产者被通知可以生产后,立马生产并打印出了第十只鸡,所以造成了生产者连续生产了11只鸡的情况

image-20220802182750141

 

所以我在程序中加入了sleep,分别在生产者和消费者被唤醒后加了100ms,在消费者消费前加了100ms,这样就不会出现上述问题了

当有多个消费者或生产者时,在判断容器是否满了,或者空了时,不要用if来判断,要用while,因为存在虚假唤醒。

  • 虚假唤醒:

    • 在一般条件下,当线程进入wait状态下,需要其他线程调用notify方法后,线程才会从wait方法中返回, 而虚假唤醒是指线程通过其他方式,从wait方法中返回。

举一个例子:当购买车票时,线程A买票,如果发现没有余票,则会调用wait方法,线程进入等待队列中,线程B进行退票操作,余票数量加一,然后调用notify 方法通知等待线程,此时线程A被唤醒执行购票操作。如果是按照if(余票数<0)判断,程序按逻辑完全可行。

那么此时线程A的状态应该如下: (1)释放锁并阻塞

(2)等待条件cond发生

(3)获取通知后,竞争获取锁

但如果在B线程退票同时,进来一个线程C,此时由于synchronized,导致C进入阻塞状态,而当B退票后,余票>0,此时C线程立即竞争到锁余票减一,而此时A线程还处于获取竞争锁状态,继续执行,此时余票是没有的,导致系统报出异常。

  • 解决方法:

使用which方法代替if方法即可,在每一次调用都判断是否满足条件,但如果一直在which循环中也有缺点,那就是会导致CPU消耗。

 

 

解决方式2:信号灯法

设置一个标志位,如果为真就等待,如果为假就通知另一个线程

通过一个演员观众的例子来了解信号灯法

package com.thread.cooperate;
​
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
​
//生产者-->演员
class Player extends Thread{
    TV tv;
    public Player(TV tv){
        this.tv = tv;
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0){
                tv.play("声生不息");
            }else {
                tv.play("广告");
            }
​
        }
    }
}
//消费者-->观众
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv){
        this.tv = tv;
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}
//产品-->节目
class TV{
    String voice;   //节目
    boolean flag = true;   //标志位
​
    //演员表演
    public synchronized void play(String voice){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:" + voice);
        this.notifyAll();   //通知观众
        this.voice = voice;
        this.flag = !this.flag;
    }
​
    //观众观看
    public synchronized void watch(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了:" + voice);
        this.notifyAll();
        this.flag = !this.flag;
    }
}

 

 

线程池

  • 背景:我们经常创建和销毁、使用特别大量的资源,比如并发情况下的线程,对性能影响特别大

  • 思路:我们可以提前创建一些线程,放在线程池中,使用时直接获取,可以比卖你频繁地创建和销毁,实现重复利用

  • 好处:

    • 提高响应速度

    • 降低资源消耗

    • 便于线程管理

 

使用线程池:

  • ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor

    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable

    • < T >Future <T> submit(Callable<T> task) :执行任务,有返回值,一般用来执行Callable

    • void shutdown():关闭线程池

  • Executor:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

package com.thread.cooperate;
​
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class TestPool {
    public static void main(String[] args) {
        //创建线程池
        //参数是线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);
​
        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
​
        //关闭连接
        service.shutdown();
    }
}
​
class MyThread implements Runnable{
​
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

image-20220803091832291

 

posted @ 2022-08-03 09:41  GrowthRoad  阅读(135)  评论(0编辑  收藏  举报