首页 > 其他分享 >多线程学习第五篇

多线程学习第五篇

时间:2023-09-02 23:11:49浏览次数:36  
标签:tv void 学习 生产者 线程 new 第五篇 多线程 public

5、线程协作(线程通信)

应用场景:生产者和消费者问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
  • 如果仓库中没有产品,则将生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费,直到仓库中再次放入产品为止。

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

  • 对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费.
  • 在生产者消费者问题中,仅有synchronized是不够的

synchronized :可阻止并发更新同一个共享资源,实现了同步,不能用来实现不同线程之间的通信

5.1、解决线程通信的几个方法

方法 作用
wait() 表示线程一直邓艾,直到其他线程通知,与sleep()不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注意: 均是Object类的方法,都只能在同步方法或者同步代码快中使用,否则会抛出异常 illegalMonitorStateException

5.2、解决线程之间通信的方式

5.2.1、管程法

并发写作模型""生产者/消费者模式""–>管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区

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

案例:

package com.thread.communication;

// 测试 生产者消费者模型 --> 利用缓冲区解决:管程法

public class TestPC {
    public static void main(String[] args) {
        SynBuffer synBuffer = new SynBuffer();

        new Producer(synBuffer).start();
        new Consumer(synBuffer).start();
    }
}

// 生产者
class Producer extends Thread{
    SynBuffer buffer;
    public Producer(SynBuffer buffer){
        this.buffer = buffer;
    }
    // 生产

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                buffer.push(new Chicken(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产了" + i +"只鸡");
        }
    }
}

// 消费者
class Consumer extends Thread{
    SynBuffer buffer;
    public Consumer(SynBuffer buffer){
        this.buffer = buffer;
    }

    // 消费

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                System.out.println("消费了-->" + buffer.pop().id +"只鸡");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 产品
class Chicken{
    int id; // 产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

// 缓冲区
class SynBuffer{

    //容器大小
    Chicken[] chickens = new Chicken[10];
    // 容器计数器
    int count = 0;

    // 生产者放入产品
    public synchronized void push(Chicken chicken) throws InterruptedException {
        // 如果容器满了,需要等待消费者消费
        if(count == chickens.length){
            // 通知消费者消费,生产等待
            this.wait();
        }
        // 如果没有满,需要丢入产品
        chickens[count] = chicken;
        count ++;
        // 可以通知消费者消费了
        this.notifyAll();
    }

    // 消费者消费产品
    public synchronized Chicken pop() throws InterruptedException {
        // 判断能否消费
        if(count == 0){
            /// 等待生产者生产,消费者等待
            this.wait();
        }
        // 如果可以消费
        count --;
        Chicken chicken = chickens[count];
        // 吃完了,通知生产者生产
        this.notifyAll();

        return chicken;
    }
}

5.2.2 信号灯法

package com.thread.communication;

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){
                this.tv.play("节目一:新闻联播");
            }else{
                this.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{
    // 演员表演,观众等待 T
    // 观众观看,演员等待 F
    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;
    }
}

5.3、使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 优点:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理…
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间会终止

JDK5.0起提供了线程池相关API:ExecutorServiceExecutors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
  • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
  • Future submit (Callable task):执行任务,有返回值,一般用来执行Callable
  • void shutdown():关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

案例:

package com.thread.communication;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //2.执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //3.关闭链接
        service.shutdown();
    }

}


class MyThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

标签:tv,void,学习,生产者,线程,new,第五篇,多线程,public
From: https://www.cnblogs.com/wangshow/p/wangshow_4.html

相关文章

  • 多线程学习第三篇
    3、线程状态线程五大状态:创建状态:通过new创建线程就绪状态:通过start()启动线程进入就绪状态阻塞状态:通过CPU调配进入运行状态运行状态:在运行状态时,可以进行如sleep,wait等方法使线程进入阻塞状态死亡状态:自然执行完毕、外部干涉终止线程具体流程为:3.1、线程的常用......
  • 设计模式学习1 设计原则
    设计原则1.开闭原则对扩展开放,修改关闭。在程序需要扩展的时候,不能去修改原有代码,实现一个热插拔的效果。为了使程序的扩展性好,易于维护和升级为了达到这样的效果,我们需要使用接口和抽象类2.里氏代换原则任何基类可以出现的地方,子类一定可以出现。也就是子类继承父类时,除了添......
  • 多线程第一篇(认识多线程)
    多线程任务,进程,线程,多线程Process:进程Thread:线程1、基本概念进程在操作系统中运行的程序就是进程。程序是指令和数据的有序集合,其本身没有任何运行的含义,是静态的。进程就是执行程序的一次执行过程,它是一个动态的概念式系统资源分配的单位通常再一个进程中可......
  • 魔鬼冲刺学习笔记
    \[\huge{\textbf{魔鬼冲刺}\quad\textbf{2023.8.31-?}}\]高二是大部分OIer的最后一段竞赛时光,这真是“\(One\Last\Olympiad\)”了。所以我们开始魔鬼冲刺了!这里就用来记录这段时期的一些收获,还有学到的知识。由于停课后学习笔记给人的感觉略显凌乱,故在本文中笔者简......
  • 每周总结-第八周 多线程
    多线程概述:充分利用计算机资源,同时执行不同的操作1.计算机操作系统进程和线程2.使用java来完成多线程的编码3.线程中的常用方法4.线程同步(重点)5.死锁6.生产者消费者模型异步操作系统简介操作系统:本质上就是一个运行在一堆硬件上的巨型软件没有操作系统的话,程序想要操控......
  • 《C和指针》学习笔记
    C和指针学习笔记前置条件1.1配置环境下载vscode安装编译器:这里以MinGw-w64为例。下载MinGw-w64的安装包并解压。添加到系统环境编辑tasks.json(该文件负责项目的编译,如果需要同时编译多个文件,需要对该文件进行如下注释内的修改):{"tasks":[{......
  • 折半搜索 学习笔记
    关于算法折半搜索,又称meetinthemiddle算法。顾名思义,就是将整个搜索的过程分成两个部分分别进行搜索,然后再将两个部分搜索出来的答案进行合并,得到最终的答案。dfs搜索算法一般都是指数级别的,那么我们假如每次dfs时都有两种决策,那么我们执行dfs算法的时间复杂度为\(O......
  • 【学习笔记】二分图基础
    二分图与网络流基础(网络流待学)查看目录目录前置知识:二分图:二分图的定义:二分图的判定:例题:[NOIP2010提高组]关押罪犯二分图的匹配:匈牙利算法:例题:[ABC317G]Rearranging[ABC317G]Rearranging前置知识:tarjan强连通分量:有向图中几个点可以相互到达,就称这几个点是强连通......
  • 莫队学习笔记(如何处理增量)
    题目传送门:序列考虑我们已经求出了区间\([l,r]\)的答案,现在要求\([l,r+1]\)的答案。很明显增多的子序列有\((l,r+1),(l+1,r+1)...(r+1,r+1)\)。考虑求出\([l,r+1]\)中的最小值的位置\(p\)(可以用\(rmq~O(1)\)求出),那么\(a_p\)的贡献就是\(a_p\times(p-l+1)\),现在我......
  • 多线程|饿汉模式和懒汉模式
    单例模式是只有单个实例的模式,应用在只能有一个实例的场景中。单例模式有很多种,这里介绍饿汉模式和懒汉模式两个单例。一、饿汉模式“饿汉”是一种形象的描述,“饿汉”看到吃的就非常急切,把这种急切的形象类比到Java中就是在类加载阶段就把实例创建出来了。什么是类加载?Java代码......