(Java高级教程)第一章Java多线程基础-第一节6:多线程案例

news2025/8/12 0:08:24

文章目录

  • 一:单例模式
    • (1)设计模式概述
    • (2)单例模式概述
    • (3)单例模式实现
      • A:饿汉模式
      • B:懒汉模式
        • ①:单线程版
        • ②:多线程版
        • ③:多线程版(改进)
  • 二:阻塞队列
    • (1)生产者与消费者模型
    • (2)使用标准库中的阻塞队列完成
    • (3)使用循环队列实现阻塞队列
      • A:关于循环队列
      • B:实现

一:单例模式

(1)设计模式概述

设计模式(Design pattern):设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的,可以把设计模式比作软件开发中的“棋谱

有关设计模式详见:设计模式|菜鸟教程
在这里插入图片描述

(2)单例模式概述

单例模式(Singleton Pattern): 是Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

  • 单例类只能有一个实例
  • 单例类必须给自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一实例

优缺点

  • 优点:节省内存开销,避免对资源的多重占用
  • 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面如何实例化

引用实例

  • 一个班只能有一个班主任
  • Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行
  • 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件

(3)单例模式实现

单例模式实现:实现单例模式时注意以下几点

  • 需要将构造方法私有化
  • 需要使用一个方法返回实例,该方法是获取该类的唯一实例的唯一入口

A:饿汉模式

饿汉模式:类加载的同时创建实例

class Singleton{
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
    //构造方法私有化
    private Singleton(){}
}

public class TestDemo {
    public static void main(String[] args) {
        //单例
        Singleton instance = Singleton.getInstance();
        //下面写法会被禁止
        //Singleton instance = new Singleton();
    }
}

B:懒汉模式

懒汉模式:类加载的同时不创建实例,第一次使用时才创建。懒汉模式,可以提高效率

  • 如果代码没有调用getInstance(),那么实例化过程就无需进行
  • 即使后续代码调用getInstance(),但由于调用调用时机可能较晚,所以创建实例的时机也就迟了,这样就把它和其他耗时操作分开

懒汉模式分为单线程多线程两个版本

①:单线程版

class SingletonLazy{
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
    //构造方法私有化
    private SingletonLazy(){}
}

②:多线程版

饿汉模式和懒汉模式的单线程版本实际上时线程不安全,这主要会发生在首次创建实例时,如果在多个线程中同时调用getInstance方法,就有可能创建出多个实例

例如

class SingletonLazy{
    private static SingletonLazy instance = null;

    public synchronized static SingletonLazy getInstance(){
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
    //构造方法私有化
    private SingletonLazy(){}
}

public class TestDemo2 {
    public static void main(String[] args) {
        SingletonLazy instance = SingletonLazy.getInstance();
    }
}

或者

class SingletonLazy{
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        synchronized (SingletonLazy.class){
            if(instance == null){
                instance = new SingletonLazy();
            }
            return instance;
        }
    }
    //构造方法私有化
    private SingletonLazy(){}
}

public class TestDemo2 {
    public static void main(String[] args) {
        SingletonLazy instance = SingletonLazy.getInstance();
    }
}

③:多线程版(改进)

上述加锁的方式其实是一种“无脑式”的加法,因为线程并不是在任何时候都是不安全的。一旦实例创建完毕,后面即便有线程调用getInstance也不会涉及线程安全问题。所以对于加锁我们只在需要的时候加即可,做出如下改进

  • 使用双重if 进行判定,降低锁竞争频率
  • 使用volatile修饰instance
class SingletonLazy{
    private static volatile SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        if(instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
     	return instance;
        }
    }
    //构造方法私有化
    private SingletonLazy(){}
}

public class TestDemo2 {
    public static void main(String[] args) {
        SingletonLazy instance = SingletonLazy.getInstance();
    }
}

关于双重if判定,需要说明

  • 外层if用于判定是否要加锁:一旦instance已经存在,通过该层判定就可得知,所以就不会再尝试获取锁了,降低了开销
  • 里层if用于判定是否需要创建实例

二:阻塞队列

阻塞队列:这是一种特殊的队列,也遵守先进先出的原则

  • 当队列满的时候,继续入队列就会阻塞,直到其他线程从队列中取走元素
  • 当队列空的时候,继续出队列就会阻塞,直到其他线程从队列中插入元素

阻塞队列的一个典型应用场景就是生产者与消费者模型

(1)生产者与消费者模型

在现实生活中,当我们缺少某些生活用品时,就回到超市去购买。当你到超市时,你的身份就是消费者,那么这些商品又是哪里来的呢,自然是供应商,那么它们就是生产者,而超市在生产者与消费者之间,就充当了一个交易场所。正是这样的方式才使得人类的交易变得高效,生产者只需要向超市供应商品,消费者只需要去超市购买商品

计算机是现实世界的抽象,因此像这种人类世界的模型,自然也被引入到了计算机当中。在实际软件开发中,进程或线程就是生产者和消费者,他们分别产生大量数据或消耗大量数据,但是他们之间一般不直接进行交流,而是生产者生产好数据之后把数据交到一个缓冲区中,消费者需要数据时直接从缓冲区中取就可以了
在这里插入图片描述
我们将其总结为321原则——3种关系,2个角色,1个场所

  • 3种关系:生产者与生产者之间是互斥关系,消费者与消费者之间是互斥关系,生产者与消费者之间是同步关系
  • 2个角色:生产者和消费者
  • 1个场所:它们之间进行数据交互是在一缓冲区当中,这个缓冲区可以有多种表现形式

(2)使用标准库中的阻塞队列完成

BlockingQueue:这一个接口,真正实现的类是LinkedBlockingQueue

  • put方法用于阻塞式的入队列
  • tabke用于阻塞式的出队列
import java.util.Random;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

public class ProductorAndConsumer {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<Integer>();
        Thread productor = new Thread(){
            Random random = new Random();
            @Override
            public void run(){
                while(true) {
                    try {
                        if(blockingDeque.size() <= 5){
                            int product = random.nextInt(1000);
                            System.out.println("生产者生产产品:" + product);
                            //入队列,每1s产生一个产品
                            blockingDeque.put(product);
                        }else{
                            System.out.println("队列已满请消耗完再放入");
                        }

                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        Thread consumer = new Thread(){
            @Override
            public void run(){
                while(true){
                    try {
                        //出队列,每2s消耗一个产品
                        int value = blockingDeque.take();
                        System.out.println("消费者消费产品:" + value);
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                }
            }
        };

        productor.start();
        consumer.start();

        productor.join();
        consumer.join();
    }
}

请添加图片描述

(3)使用循环队列实现阻塞队列

A:关于循环队列

  • 关于循环队列的定义见这篇文章

使用循环队列实现时,生产者将产品放入tail所指位置,消费者消费head所指位置处的产品

在这里插入图片描述

如下是循环队列的基本框架,但并未实现阻塞

class MyBlockingQueue{
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    //入队列
    public void put(int elem){
        if(size >= items.length){
            return;
        }
        items[tail] = elem;
        tail++;
        if(tail >= items.length){
            tail = 0;
        }
        size++;
    }

    //出队列
    public Integer take(){
        if(size == 0){
            return null;//返回无效值
        }
        int res = items[head];
        head++;
        if(head >= items.length){
            head = 0;
        }
        size--;

        return res;
    }
}

B:实现

在上面代码的基础上,加入阻塞逻辑

  • 生产者在生产产品时,如果队列已满,需要调用wait()阻塞,直到队列不满;放入产品后需要调用notifyAll()通知消费者消费
  • 消费者在消费产品时,如果队列为空,需要调用wait()阻塞,直到队列不空;消费产品后需要调用notifyAll()通知生产者生产

注意

  • 使用volatile修饰headtailsize
  • 不要忘记synchronized
  • 判断队空或队满时,应该使用while,使其被唤醒时确确实实是对已经不空或者不满了,否则被唤醒时可能由于条件仍然不满足,而被迫还得继续等待

如下代码中,消费者每1s消费一个产品,生产者每0.5s生产一个产品

import java.util.Random;

class MyBlockingQueue{
    private int[] items = new int[1000];
    private volatile int head = 0;
    private volatile int tail = 0;
    private volatile int size = 0;

    //入队列
    public void put(int elem) throws InterruptedException {
        synchronized (this) {
            while (size == items.length) {
                // 队列满,阻塞等待
                this.wait();
            }
            items[tail] = elem;
            tail++;
            if (tail >= items.length) {
                tail = 0;
            }
            size++;
            // 通知消费者消费
            this.notifyAll();
        }
    }

    //出队列
    public Integer take() throws InterruptedException {
        synchronized (this) {
            while (size == 0) {
                // 队列空,阻塞等待
                this.wait();
            }
            int res = items[head];
            head++;
            if (head >= items.length) {
                head = 0;
            }
            size--;
            // 通知生产者生产
            this.notifyAll();
            return res;
        }
    }
}

public class TestDemo3 {
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue myBlockingQueue = new MyBlockingQueue();
        Thread consumer = new Thread(){
            @Override
            public void run(){
                while(true) {
                    try {
                        int value = myBlockingQueue.take();
                        System.out.println("消费者获取产品:" + value);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        consumer.start();

        Thread producotr = new Thread(){
            Random random = new Random();
            @Override
            public void run(){
                while(true) {
                    try {
                        int value = random.nextInt(1000);
                        myBlockingQueue.put(value);
                        System.out.println("生产者生产产品:" + value);
                        Thread.sleep(500);

                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        producotr.start();

        consumer.join();
        producotr.join();
        
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/15291.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

alpha模型:打开量化投资的黑箱;附创业板布林带策略代码:年化15%。

原创文章第108篇&#xff0c;专注“个人成长与财富自由、世界运作的逻辑&#xff0c; AI量化投资”。 关于量化投资&#xff0c;我们写了不少文章。从数据准备&#xff0c;预处理&#xff0c;因子特征工程&#xff0c;因子分析&#xff0c;规则策略&#xff0c;模型&#xff0c…

平安城市解决方案-最新全套文件

平安城市解决方案-最新全套文件一、建设背景目前平安城市视频监控面临的主要问题&#xff1a;1、看不清2、传不回3、存不下4、找不着5、易泄露二、思路架构三、建设方案四、获取 - 平安城市全套最新解决方案合集一、建设背景 平安城市是一个特大型、综合性非常强的管理系统&am…

JSP文件上传

JSP 提供了上传和下载的功能&#xff0c;用户釆用此功能&#xff0c;可以轻松实现文件的传输。下面介绍文件上传与下载的操作。 用户通过一个 JSP 页面上传文件给服务器时&#xff0c;该 JSP 页面必须含有 File 类型的表单&#xff0c; 并且表单必须将 enctype 的属性值设置为…

Node.js 是怎么找到模块的?

大家好&#xff0c;我是前端西瓜哥&#xff0c;今天我们来看看 Node.js 模块查找的原理。 模块种类 模块有三种来源。 核心模块&#xff1a;Node.js 内置的包。比如 http、fs、path&#xff1b; 自定义模块&#xff1a;NPM 包。比如 axios、express&#xff0c;位于 node_mo…

virtualBox虚拟机之间网络互通设置

环境 主机&#xff1a;Win10 虚拟机&#xff1a;Ubuntu 20.04 虚拟机&#xff1a;VirtualBox 6.1 模式虚拟机→主机主机→虚拟机虚拟机↔虚拟机虚拟机→Net/LANNet/LAN→虚拟机NAT√端口转发√端口转发NATservice√端口转发√√端口转发Host-Only√√√Internal√Bridged√√…

【快速上手系列】用于登录的验证码制作(ValidateCode)和Javaweb自带的老式验证码快速上手

【快速上手系列】用于登录的验证码制作&#xff08;ValidateCode&#xff09;和Javaweb自带的老式验证码快速上手 验证码 简介 验证码&#xff08;CAPTCHA&#xff09;是“Completely Automated Public Turing test to tell Computers and Humans Apart”&#xff08;全自动…

易基因|疾病研究:DNA甲基化和转录组学特征在高浆卵巢癌复发和耐药过程中高度保守

易基因&#xff5c;疾病研究&#xff1a;DNA甲基化和转录组学特征在高浆卵巢癌复发和耐药过程中高度保守 大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 2022年07月27日&#xff0c;《J Exp Clin Cancer Res》杂志发表了题为“DNA me…

Linux|centos7下部署安装alertmanager并实现邮箱和微信告警(三)

前言&#xff1a; 前文Linux|centos7下部署安装alertmanager并实现邮箱和微信告警&#xff08;二&#xff09;_晚风_END的博客-CSDN博客 实现了告警系统模块的部署和测试&#xff0c;主要的告警范围是服务器节点的操作系统内存&#xff0c;磁盘空间的使用率这些方面&#xff0…

甘露糖-聚乙二醇-N-羟基琥珀酰亚胺mannose-PEG-NHS

甘露糖-聚乙二醇-N-羟基琥珀酰亚胺mannose-PEG-NHS 琥珀酰亚胺又称为丁酰亚胺或丁二酰亚胺&#xff0c;是一种无色针状结晶或具有淡褐色光泽的薄片固体&#xff0c;味甜。易溶于水、醇或氢氧化钠溶液&#xff0c;不溶于醚、氯仿等&#xff0c;可以提供PEG接枝修饰甘露糖&#…

精彩回顾!2022VisionChina深圳展圆满落幕

11月16日&#xff0c;维科杯•OFweek 2022中国工业自动化及数字化行业年度评选颁奖典礼在深圳大中华喜来登酒店举行&#xff0c;经过OFweek网络投票、专家组评审及组委会综合评审三轮激烈紧张的评选&#xff0c;昂视凭借“PiqsVT智能视觉系统”在近300个参评项目中脱颖而出&…

Linux三个踩坑过程记录

今早花了一早上的时间解决了三个Linux的问题&#xff0c;分别是读写权限、克隆虚拟机开启问题和Xshell连接VM虚拟机问题。平时用虚拟机比较少&#xff0c;现在刚一开始用&#xff0c;就给我来了三个问题让我解决&#xff0c;真是含泪解决问题&#xff0c;但现在解决了&#xff…

预定2.0 Crack ZoomCharts JavaScript 最值得探索

世界上最可探索的 JavaScript 图表 将内容深入分析和支持多点触控的大数据可视化轻松集成到您的 Web 项目中--ZoomCharts JavaScript 快速、简单且令人印象深刻的 JavaScript 图表 以极快的速度将 javascript 图表与令人惊叹的向下钻取功能集成&#xff0c;一定会给您的团队、…

Word控件Spire.Doc 【文本】教程(22) ;在 Word 中应用强调标记(C#/VB.NET)

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

Live800:在线客服系统如何帮助企业创造持续的服务价值?

德鲁克管理箴言&#xff1a;企业的唯一目的就是“创造顾客”。 如何创造顾客&#xff1f;只能依靠产品和服务。产品和服务是连接企业与客户的天然纽带和必然桥梁。 企业依靠持续不断生产满足客户需求、符合客户价值主张的优质产品和服务来创造客户、留住客户并建立客户忠诚度…

视频怎么添加水印?快来收好这些方法

如今短视频行业发展得如火如荼&#xff0c;很多小伙伴都投入了短视频制作中。我最初发布视频只是因为热爱记录日常&#xff0c;顺便还能增进一下自己的视频剪辑水平。刚开始没想那么多&#xff0c;视频制作好就直接传到平台上去了&#xff0c;后来才发现自己的视频被别人搬运&a…

高通量筛选化合物库抑制缺氧诱导因子抑制剂

研究证明了土著细菌具有铁依赖性机制&#xff0c;可以抑制宿主铁的运输和储存。通过微生物代谢物的高通量筛选&#xff0c;研究人员发现&#xff0c;肠道菌群产生的代谢物能抑制肠道铁吸收主要转录因子低氧诱导因子 2α (HIF-2α)&#xff0c;并增加铁存储蛋白铁蛋白 (Ferritin…

SpringBoot SpringBoot 开发实用篇 4 数据层解决方案 4.3 H2数据库

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇4 数据层解决方案4.3 H2数据库4.3.1 问题引入4.3.2 内置数据库4.3.3 …

微信小程序使用npm教程

首先打开工具-详情-勾选使用npm模块 使用npm的步骤&#xff1a; 1. 安装 npm 包 在小程序 package.json 所在的目录中执行命令安装 npm 包&#xff1a; npm install此处要求参与构建 npm 的 package.json 需要在 project.config.json 定义的 miniprogramRoot 之内。 tips&am…

【大数据存储技术】思考题及参考答案

文章目录第1章 绪论1. NoSQL和关系型数据库在设计目标上有何主要区别&#xff1f;2. 简要总结一下NoSQL数据库的技术特点。第2章 NoSQL数据库的基本原理1. 描述分布式数据管理的特点。2. 什么是CAP原理&#xff1f;CAP原理是否适用于单机环境&#xff1f;3. 简述BASE理论的具体…

【附源码】计算机毕业设计JAVA校园疫情防控管理软件

【附源码】计算机毕业设计JAVA校园疫情防控管理软件 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA…