Java之线程篇七

news2024/10/1 14:09:40

目录

单例模式

饿汉模式

懒汉模式-单线程版

懒汉模式-多线程版

阻塞队列

生产者消费者模型

标准库中的阻塞队列

阻塞队列实现

定时器

标准库中的定时器

实现定时器

线程池

标准库中的线程池

Executors 创建线程池的几种方式

线程池的优点

ThreadPoolExecutor的构造方法


单例模式

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种.

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

代码示例

// 期望这个类能够有唯一一个实例.
class Singleton {
    private static Singleton instance = new Singleton();

    // 通过这个方法来获取到刚才的实例.
    // 后续如果想使用这个类的实例, 都通过 getInstance 方法来获取.
    public static Singleton getInstance() {
        return instance;
    }

    // 把构造方法设置为 私有 . 此时类外面的其他代码, 就无法 new 出这个类的对象了.
    private Singleton() { }
}

public class Demo17 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

运行结果

懒汉模式-单线程版
类加载的时候不创建实例 . 第一次使用的时候才创建实例 .
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}
懒汉模式-多线程版
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

懒汉模式-多线程版改进

使用双重 if 判定 , 降低锁竞争的频率 .
instance 加上了 volatile.
class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
           if (instance == null) {
               instance = new Singleton();
               }
           }
       }
        return instance;
   }
}

上述代码使用volatile的作用

1.防止指令重排序:在多线程环境中,JVM 和现代处理器可能会对代码进行优化,以提高执行效率。这种优化可能包括指令重排序,即改变指令的执行顺序。在 Singleton 的实例化过程中,如果 instance 变量没有被声明为 volatile,那么 JVM 可能会将 instance = new Singleton(); 这行代码分解为三个操作:
分配内存给 instance。
调用 Singleton 的构造函数来初始化对象。
将 instance 指向分配的内存地址。

如果没有 volatile,这三个操作可能会被重排序,导致某个线程在 instance 被正确初始化之前就观察到 instance 不为 null 的情况,但此时 instance 指向的对象可能还没有完全初始化(即构造函数还未完全执行完毕)。这被称为“部分初始化”问题,可能导致程序出现不可预测的行为。通过声明 instance 为 volatile,JVM 会保证 instance 的赋值操作不会被重排序到构造函数调用之前,从而避免这个问题。

2.保证可见性:volatile 关键字还确保了不同线程之间对 instance 变量的可见性。即,当一个线程修改了 instance 的值(从 null 变为指向一个 Singleton 实例的引用),这个修改会立即对其他线程可见。没有 volatile,一个线程可能无法看到另一个线程对 instance 的修改,从而导致它错误地创建另一个 Singleton 实例。

阻塞队列
阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则. 
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素. 
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素. 

阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型. 

生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取. 

1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.
2) 阻塞队列也能使生产者和消费者之间 解耦.

标准库中的阻塞队列
Java 标准库中内置了阻塞队列 . 如果我们需要在一些程序中使用阻塞队列 , 直接使用标准库中的即可 .
BlockingQueue 是一个接口 . 真正实现的类是 LinkedBlockingQueue.
put 方法用于阻塞式的入队列 , take 用于阻塞式的出队列 .
BlockingQueue 也有 offer, poll, peek 等方法 , 但是这些方法不带有阻塞特性 .

 代码示例

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class Demo19 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        queue.put("111");
        queue.put("222");
        queue.put("333");
        queue.put("444");
        String elem = queue.take();
        System.out.println(elem);
        elem = queue.take();
        System.out.println(elem);
        elem = queue.take();
        System.out.println(elem);
        elem = queue.take();
        System.out.println(elem);
        elem = queue.take();
        System.out.println(elem);
    }
}

运行结果

阻塞队列实现

通过 "循环队列" 的方式来实现. 
使用 synchronized 进行加锁控制. 
put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程). 
take 取出元素的时候, 判定如果队列为空, 就进行 wait.

class MyBlockingQueue{
    private String[] data=new String[1000];
    private volatile int head=0;//队列起始位置
    private volatile int tail = 0;//队列结束位置的下一个位置
    private volatile int size=0;//队列中有效元素的个数

    public void put(String elem) throws InterruptedException {
        synchronized (this){
            while(size==data.length){
                this.wait();
            }
            data[tail]=elem;
            tail++;
            if(tail==data.length)
                tail=0;
            size++;
            this.notify();
        }
    }

    public String take() throws InterruptedException {
        synchronized (this){
            while(size==0){
                this.wait();
            }
            String ret=data[head];
            head++;
            if(head== data.length)
                head=0;
            size--;
            this.notify();
            return ret;
        }
    }
}

public class Demo18 {
    public static void main(String[] args) {
        MyBlockingQueue queue=new MyBlockingQueue();

        //消费者
        Thread t1=new Thread(()->{
            while(true){
                try {
                    String result=queue.take();
                    System.out.println("消费元素:"+result);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        //生产者
        Thread t2=new Thread(()->{
            int num=1;
            while(true){
                try {
                    queue.put(num+"");
                    System.out.println("生产元素:"+num);
                    num++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        t2.start();
    }
}

 运行结果

定时器
定时器也是软件开发中的一个重要组件 . 类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定好的代码.
标准库中的定时器
标准库中提供了一个 Timer . Timer 类的核心方法为 schedule .
schedule 包含两个参数 . 第一个参数指定即将要执行的任务代码 , 第二个参数指定多长时间之后执行 ( 单位为毫秒 ).
代码示例
import java.util.Timer;
import java.util.TimerTask;

public class Demo20 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        //给定时器安排一个任务,预定在某个时间去执行
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        },1000);
        System.out.println("程序启动");
    }
}
运行结果
实现定时器
定时器的构成:
1.一个带优先级的阻塞队列
为啥要带优先级呢? 
因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带
优先级的队列就可以高效的把这个 delay 最小的任务找出来. 
2.队列中的每个元素是一个 Task 对象.
3.Task 中带有一个时间属性, 队首元素就是即将要执行的任务.
4.同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
代码示例
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.TimerTask;

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable runnable;
    private long time;
    public MyTimerTask(Runnable runnable,long delay){
        this.runnable=runnable;
        this.time=System.currentTimeMillis()+delay;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time-o.time);
    }

    public long getTime(){
        return time;
    }

    public Runnable getRunnable() {
        return runnable;
    }
}

class MyTimer{
    private PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();
    private Object locker=new Object();

    public void schedule(Runnable runnable,long delay){
        synchronized (locker){
            queue.offer(new MyTimerTask(runnable,delay));
            locker.notify();
        }
    }

    public MyTimer() {
        Thread t=new Thread(()->{
            while(true) {
                try {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            task.getRunnable().run();
                            queue.poll();
                        } else {
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
}

public class Demo21 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        }, 3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        }, 2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        }, 1000);
        System.out.println("程序开始执行");
    }
}
线程池
线程池最大的好处就是减少每次启动、销毁线程的损耗。
标准库中的线程池
使用 Executors.newFixedThreadPool(4) 能创建出固定包含 4 个线程的线程池. 
返回值类型为 ExecutorService
通过 ExecutorService.submit 可以注册一个任务到线程池中.

代码示例

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

public class Demo22 {
    public static void main(String[] args) {
        ExecutorService service= Executors.newFixedThreadPool(4);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

 运行结果

Executors 创建线程池的几种方式

newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数目动态增长的线程池.
newSingleThreadExecutor: 创建只包含单个线程的线程池. 
newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer. 

Executors 本质上是 ThreadPoolExecutor 类的封装. 

实现线程池

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPool{
    private BlockingQueue<Runnable> queue=new ArrayBlockingQueue<>(1000);
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    public MyThreadPool(int n){
        for (int i = 0; i < n; i++) {
            Thread t=new Thread(()->{
                Runnable runnable= null;
                try {
                    runnable = queue.take();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                runnable.run();
            });
            t.start();
        }
    }
}

public class Demo23 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool=new MyThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            int id=i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务:"+id);
                }
            });
        }
    }
}

运行结果

线程池的优点

1.减少资源消耗:通过重用已存在的线程,线程池避免了线程创建和销毁所带来的开销。线程创建和销毁是昂贵的操作,因为它们涉及到系统资源的分配和释放。使用线程池可以显著减少这些开销,提高系统的资源利用率。
2.提高响应速度:由于线程池中的线程是预先创建好的,当有新任务到来时,可以立即分配线程去执行,而不需要等待新线程的创建。这可以显著提高系统的响应速度,尤其是在高并发场景下。
3.提高线程的可管理性:线程池提供了一种集中管理线程的方式,包括线程的创建、销毁、调度等。通过线程池,开发者可以更容易地控制系统中线程的数量,避免创建过多的线程导致系统资源耗尽。
4.提供灵活的配置选项:大多数线程池实现都提供了丰富的配置选项,如线程池的大小、任务的队列类型、拒绝策略等。这些配置选项使得开发者可以根据应用程序的具体需求来优化线程池的性能。
5.简化并发编程:线程池隐藏了线程管理的复杂性,使得开发者可以更加专注于业务逻辑的实现,而不是线程的管理。这简化了并发编程的难度,降低了出错的可能性。
6.支持并发任务的执行:线程池可以同时执行多个任务,提高了系统的并发处理能力。这对于需要处理大量并发请求的应用程序来说是非常重要的。
7.提供任务调度功能:一些高级的线程池实现还提供了任务调度的功能,允许开发者按照特定的策略(如定时、周期性等)来执行任务。这进一步增强了线程池的灵活性和功能。

ThreadPoolExecutor的构造方法

 上图中最后一个构造函数功能最多,以这个为介绍对象:

corePoolSize:核心线程数

maximumPoolSize:最大线程数

线程池里面的线程数目:[corePoolSize,maximumPoolSize]

keepAlive:允许线程最大的存活时间

unit:时间单位

workQueue:阻塞队列,用来存放线程池中的任务

threadFactory:工厂模式

handler:拒绝策略

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

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

相关文章

Zotero7+better notes+Ethereal Style自定义笔记模版

插件版本 zotero: v7.0.5 Ethereal Style: v4.9.8 Better Notes for Zotero: v2.0.8 Green Frog: v0.19.05 笔记模版样式 模版代码 <!-- zotero: v7.0.5Ethereal Style: v4.9.8Better Notes for Zotero: v2.0.8Green Frog: v0.19.05 --><!-- 标题 -->&l…

Windows平台如何实现RTSP|RTMP流录像?

好多开发者使用场景&#xff0c;除了实现基础的低延迟RTSP、RTMP播放外&#xff0c;还需要实现RTSP、RTMP流数据的本地录像功能。本文以大牛直播SDK的Windows平台播放模块为例&#xff0c;介绍下如何实现RTSP、RTMP流录像。 功能设计 [拉流]支持拉取RTSP流录像&#xff1b; [拉…

51单片机的宠物自动投喂系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温湿度传感器DS1302时钟模块蓝牙步进电机按键、蜂鸣器等模块构成。适用于猫猫/狗狗宠物自动喂食器等相似项目。 可实现基本功能: 1、LCD1602实时显示北京时间和温湿度 2、温湿度传感器DHT11采集环境温湿度 3、时…

蓝桥杯【物联网】零基础到国奖之路:十四. 扩展模块之温湿度传感器

蓝桥杯【物联网】零基础到国奖之路:十四. 扩展模块之温湿度传感器 第一节 硬件解读第二节 CubeMX配置第三节 模版代码 第一节 硬件解读 STS3x-DIS是sensirion新一代温湿度传感器。精度较高&#xff0c;速度较快。SHT3x内部集成了湿度传感器和温度传感器&#xff0c;ADC采样输入…

shell脚本多行注释

1.冒号<<结束字符 :<<COMMENT echo -e&#xff1a;使用 -e 参数使 echo 支持转义字符。 \n&#xff1a;表示换行。 # Source definitions&#xff1a;添加注释。 . /etc/profile&#xff1a;加载 /etc/profile 文件。 >> 将上述内容追加到 /root/.bashrc 文…

通过PHP获取商品详情

在电子商务的浪潮中&#xff0c;数据的重要性不言而喻。商品详情信息对于电商运营者来说尤为宝贵。PHP&#xff0c;作为一种广泛应用的服务器端脚本语言&#xff0c;为我们提供了获取商品详情的便捷途径。 了解API接口文档 开放平台提供了详细的API接口文档。你需要熟悉商品详…

数据结构——栈的基本操作

前言 介绍 &#x1f343;数据结构专区&#xff1a;数据结构 参考 该部分知识参考于《数据结构&#xff08;C语言版 第2版&#xff09;》55 ~ 59页 &#x1f308;每一个清晨&#xff0c;都是世界对你说的最温柔的早安&#xff1a;ૢ(≧▽≦)و✨ 1、栈的基本概念 栈&#x…

LabVIEW提高开发效率技巧----RT与FPGA模块

LabVIEW RT&#xff08;Real-Time&#xff09;和FPGA模块是为开发实时系统和高性能控制应用而设计的&#xff0c;能够有效满足工业控制、自动化测试、信号处理等领域的严格要求。通过这两个模块&#xff0c;开发者可以充分发挥LabVIEW的并行处理能力&#xff0c;实现高效稳定的…

L0-Linux-关卡材料提交

SSH全称Secure Shell&#xff0c;中文翻译为安全外壳&#xff0c;它是一种网络安全协议&#xff0c;通过加密和认证机制实现安全的访问和文件传输等业务。SSH 协议通过对网络数据进行加密和验证&#xff0c;在不安全的网络环境中提供了安全的网络服务。 SSH 是&#xff08;C/S…

基于SpringBoot+Vue的学生宿舍管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

中国雕塑——孙溟㠭浅析《瘗鹤铭》

中国雕塑——孙溟㠭浅析碑帖《瘗鹤铭》 《瘞鹤铭》 《瘗鹤铭》是原刻于镇江焦山西麓崖壁上的摩崖石刻&#xff0c;属楷书体。是一位隐士为一只死去的鹤所撰的纪念文字。时代和书写者众说纷纭&#xff0c;前人有说晋王羲之&#xff0c;有说梁代陶弘景&#xff0c;有人认为是隋朝…

linux文件编程_文件

1. 文件编程概述 之前在windows中对文件的操作是&#xff1a;打开文档—>编辑文档—>保存文档—>关闭文档 我们的Linux文件编程主要是利用代码对文件进行操作&#xff1a;文件创建、打开、编辑等自动化执行等 在Linux我们要使用编程调用api函数的方式进行文档的编辑…

怎么将电脑的“任务栏”设置为“透明”状态?任务栏透明度设置攻略!扩散!

怎么将电脑的“任务栏”设置为“透明”状态? ■ 在Windows系统中&#xff0c;电脑任务栏设置为透明状态的方式有很多种&#xff0c;可以通过电脑自带的个性化设置&#xff0c;或者注册表编辑&#xff0c;还可以通过第三方软件进行任务栏丰富的修改。 在Windows操作系统中&…

【UEFI基础】BIOS下的NVMe

什么是NVMe NVMe全称NonVolatile Memory Express&#xff08;非易失性内存主机控制器接口规范&#xff09;&#xff0c;其官方&#xff08;NVMe官网NVM Express&#xff09;定义将其描述为“一个开放的标准和信息集合&#xff0c;以充分释放非易失性存储在从移动端到数据中心的…

【YOLO系列】YOLOv11正式发布!

Yolov11发布文档 代码链接 了解Ultralytics YOLO11的所有突破性功能&#xff0c;这是我们最新的人工智能模型&#xff0c;具有无与伦比的准确性和效率。 我们很高兴向大家介绍Ultralytics型号的下一次进化&#xff1a;YOLO11&#xff01;YOLO11建立在以前YOLO模型版本令人印象…

【易上手快捷开发新框架技术】用Flet从零开始分步骤循序渐进编程实现购物清单助手手机应用app示例掰烂嚼碎深度讲解源代码IDE运行和调试通过截图为证

传奇开心果编程实例微博文 序言首先&#xff0c;明确任务&#xff0c;任务驱动&#xff1a;其次&#xff0c;开发工具选型考虑&#xff1a;第三&#xff0c;编程思路和应用结构设计&#xff1a; 第一步&#xff1a;从零开始搭建移动应用雏形框架第二步&#xff1a;设置窗口大小…

PCL 移除点云边缘不连续的点

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 法向量计算 2.1.2 边界检测和移除 2.1.3 边界检测和移除 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&…

React学习笔记(4.0)

json-server实现数据Mock 1.项目中安装json-server npm i -D json-server 2.准备一个json文件 3.添加启动命令【package.json中配置】 "server":"json-server ./server/data.json --port 8888" 该命令中&#xff0c;路径就是自己创建的json文件路径&…

【C++】BitSet和Bloom_Filter

前言&#xff1a; 在计算机图形学中&#xff0c;位图&#xff08;Bitmap&#xff09;也称为光栅图&#xff0c;是由像素点组成的图像表示方式。在 C 编程中&#xff0c;位图可以通过特定的函数和数据结构来进行处理和操作。 BitMap 位图&#xff08;BitMap&#xff09;是一种数…

uniapp中uni.request的统一封装 (ts版)

文章目录 前言一、我们为什么要去封装&#xff1f;二、具体实现1.创建一个请求封装文件&#xff1a;2.封装 uni.request&#xff1a;3.如何去使用&#xff1f; 总结 前言 在uniapp中如何去更简洁高效的发送我们的请求&#xff0c;下面就介绍了uni.request()二次封装。 一、我们…