【JavaEE 初阶(三)】多线程代码案例

news2025/1/6 18:13:42

❣博主主页: 33的博客❣
▶️文章专栏分类:JavaEE◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多线程知识

在这里插入图片描述

目录

  • 1.前言
  • 2.单例模式
    • 2.1饿汉方式
    • 2.2饿汉方式
  • 3.阻塞队列
    • 3.1概念
    • 3.2实现
  • 4.定时器
    • 4.1概念
    • 4.2实现
  • 5.线程池
    • 5.1概念
    • 5.2实现
  • 6.总结

1.前言

在开发过程中,我们会遇到很多经典的场景,针对这些经典场景,大佬们就提出了一些解决方案,我们只需要按照这个解决方案来进行代码的编写,这样就不会写得很差。

2.单例模式

我们先了解什么是设计模式,在开发过程中,我们会遇到很多经典的场景,针对这些经典场景,大佬们就提出了一些解决方案,按照这个方式进行编程,代码就不会很差,就例如下期中的棋谱,如果按照棋谱下棋也是不会下很烂的。
所谓单例就是单个实例(对象),那么怎么保证一个类只有一个对象呢?我们需要通过一些编程技巧来达成这样的效果。
在一个类的内部提供一个实例,把构造方法设置为private避免再构造出新的实例

2.1饿汉方式

饿汉方式:

class Singleton{
    private static Singleton instance=new Singleton();
    public static Singleton getsingleton(){
        return instance;
    }
    private  Singleton(){

    }
}
public class Demo17 {
    public static void main(String[] args) {
      Singleton s1=Singleton.getsingleton();
      //Singleton s2=new Singleton();
    }
}

上述代码中,创建一个实例的时候在类加载的时候,太早就创建了,那可不可以晚一点呢?

2.2饿汉方式

懒汉方式:

class Singleton2{
    private static Singleton2 instance2=null;
    public static Singleton2 getInstance2(){
        if(instance2==null){
            instance2=new Singleton2();
        }
        return instance2;
    }
    private Singleton2(){};
}

但懒汉模式是不安全的,如下:
在这里插入图片描述
这样就创建了两个实例,所以我们需要对读取和修改进行加锁操作

class Singleton3{
    private static Singleton3 instance3=null;
    Object lock=new Object();
    public static Singleton3 getInstance2(){
            synchronized (lock){
                if(instance3==null){
                    instance3=new Singleton3();
                }
            }
        return instance3;
    }    
}

这样就只有在调用getInstance2方法才会去创建对象,但是又引出了新的问题,其实是否创建对象,只需要在第一次调用这个方法的时候判断,一旦创建好,以后都不用在再去判断了,可这样写,每次调用这个方法都会去判断,这样就消耗不少资源。我们进行优化:

class Singleton3{
    private static Singleton3 instance3=null;
    Object lock=new Object();
    public static Singleton3 getInstance2(){
        if(instance3==null){
            synchronized (lock){
                if(instance3==null){
                    instance3=new Singleton3();
                }
            }
        }
        return instance3;
    }
    private Singleton3(){};
}

大家以为到这儿,代码完美了吗?其实并不是,在new的时候可能会引起指令重排序问题,那么什么是指令重排序问题呢?指令重排序也是编译器为了提高执行效率,做出的优化,在保持逻辑不变的前提下,可能对编译器做出优化.
例如我们要去一个水果超市买香蕉、苹果、火龙果、猕猴桃四种水果但它们在不同的展区。
优化前:
在这里插入图片描述
优化后在这里插入图片描述
在通常情况下,在单线程中,指令重排序,就能够保证逻辑不变的情况下,把程序的效率提高,但在多线程中就不一定了,可能会误判。
new操作是可能触发指令重排序
new可以分为3步:
1.申请内存空间
2.在内存空间上构造对象(构造方法)
3.把内存地址复制给instance引用。
如果内存进程优化:
1.申请内存空间
2.把内存地址复制给instance引用。
3.在内存空间上构造对象(构造方法)
在这里插入图片描述
那么该怎么解决这个问题呢?可以使用volatile让其修饰instanse就可以保证,在修改instanse的时候就不会出现指令重排序问题。

class singleton{
    private static volatile  singleton instance=null;
    public static singleton getinstance(){
        if (instance==null){
            synchronized (singleton.class){
                if (instance==null){
                    instance=new singleton();
                }
            }
        }
        return instance;
    }
    private singleton(){}  
}
public class Demo21 {
    public static void main(String[] args) {
        singleton s1=singleton.getinstance();
    }
}

这个时候才算真正的完成一个单例模式。

3.阻塞队列

3.1概念

阻塞队列也是多线程编程中比较常见的一种数据结构,它是一种特殊的队列,它具有线程安全的特点,并且带有阻塞特性。
阻塞队列最大的意义就是用来实现“生产者消费者模型”
例如:一家人在一起包饺子,但是擀面杖只有一个,那么就指定一定人擀饺子皮就称它为生产者,每擀一张皮,就放入圆盘中,其余人都包饺子称为消费者,如果圆盘中没有饺子皮了,消费者就要等待生产,如果圆盘中放满了就要等待生产者就要等待消费。
那么为啥要引入“生产者消费者模型”呢?队我们又有什么好处呢?

1.解耦合:就是降低两个代码块的紧密程度
2.削峰填谷
那么在Java中,怎么实现阻塞队列呢?在标准库里,以及提供了线程的

 public static void main(String[] args) throws InterruptedException {
        BlockingDeque<Integer> deque=new LinkedBlockingDeque<>();
        deque.put(1);
        deque.put(2);
        deque.put(3);
        System.out.println(deque.take());
        System.out.println(deque.take());
        System.out.println(deque.take());
        System.out.println(deque.take());
    }

最后一次出队列,队列已经空了,所以就会阻塞:
在这里插入图片描述

3.2实现

既然我们已经会使用阻塞队列了,那我们能不能自己实现一下呢?我们底层可以采用循环数组来实现。

public class MyBlockingqueue {
    String[] arr=new String[20];
    private volatile int head=0;//后续中既会读又会改,为了避免内存可见性+volatile
    private volatile int end=0;
    private volatile int size=0;
    public void put(String elem) throws InterruptedException {
        synchronized (this){
            if(end==arr.length){
                this.wait();
                return;
            }
            arr[end]=elem;
            end++;
            size++;
            this.notify();//唤醒因为队列空导致的阻塞
            if(end==arr.length){
                end=0;
            }
        }

    }
    public String tack() throws InterruptedException {
        synchronized (this){
            if (size==0){
                this.wait();
            }
            String ret=arr[head];
            head++;
            size--;
            this.notify();//唤醒因为队列满导致的阻塞
            if(head==arr.length){
                head=0;
            }
            return ret;
        }
    }
}

在这里插入图片描述

4.定时器

4.1概念

定时器也是日常开发中常见的组件,约定一个时间,时间到达后就会执行某个逻辑。在Java标准库中,有一个线程的标准库。

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("定时器打开");
}

主线程执行schdule方法时,此时就把任务放到了timer中,此时timer中也也包含一个线程,叫做扫描线程,一旦时间到达就会给改线程安排任务。那么我们能不能自己实现呢?

4.2实现

1.需要定义一个类来描述一个任务,这个任务需要包含时间,和实际任务。
2.需要有一个数据结构,把任务全部存到数据库中
3.Timer中需要一个线程来描述任务是否到达时间
一个任务类:

lass  MyTimerTask implements Comparable<MyTimerTask>{
    private   Runnable runnable;
    private long time;
    public MyTimerTask(Runnable runnable,long delay){
        this.runnable=runnable;
        this.time=System.currentTimeMillis()+delay;
    }
    public long gettime(){
        return time;
    }
    public Runnable getrun(){
        return runnable;
    }
    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time-o.time);
    }
}

一个Timer类:

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

   public void schedule(Runnable runnable,long time ){
       synchronized (locker){
           queue.offer(new MyTimerTask(runnable,time));
           locker.notify();
       }
   }
   public MyTimer(){
       Thread t=new Thread(()->{
           while (true){
           try {
               synchronized (locker){
                   while (queue.isEmpty()){
                        locker.wait();
                   }
                   MyTimerTask task= queue.peek();
                   long curenttime=System.currentTimeMillis();
                   if (curenttime>=task.gettime()){
                       task.getrun().run();
                       queue.poll();
                   }else {
                       locker.wait(task.gettime()-curenttime);
                   }
               }
           }catch (InterruptedException e){
               e.printStackTrace();
             }
           }
       });
       t.start();
   }
}

测试类:

class M{
    public static void main(String[] args) {
        MyTimer myTimer=new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        },2000);
        System.out.println("开始");
    }
}

5.线程池

5.1概念

池,这个词,是计算机中一种比较重要的思想方法,很多地方都涉及到,比如内存池,进程池,连接池等等。
线程池,就是指在使用第一个线程的时候就把其他线程线程一并创建好,后续如果想要使用这个其他线程,就不必再重新创建新的线程,直接从线程池中回去即可。
那么为啥线程创建好放在池子里后续再从池子中取,比新建线程的效率更高呢?
从池子中取是纯用户态的操作,而创建线程是用户态+内核态相互配合完成的。如果一段程序是在系统内核中执行的就是叫内核态,否则为用户态。当创建线程时,就需要调用系统api,进入内核进入一系列操作,但操作系统内核不仅仅是给该线程提供服务,也要给其他线程提供服务,那么这个效率就是非常低的了。
在Java标准库中,提供了写好的线程池,直接用即可。

public static void main(String[] args) {
        ExecutorService service= Executors.newCachedThreadPool();
    }

线程池对象并不是直接new的,而是调用一个方法返回线程池对象Executors.newCachedThreadPool()称为工厂模式。
通常情况下创建一个对象需要用new关键字,new关键字会触发类的构造方法,但构造方法具有局限性,例如:在一个类中,我即能用笛卡尔坐标系来表示一个点,又能有极坐标的方法表示一个:

class point{
    //笛卡尔坐标
    public point(double x,double y){}
    //极坐标
    public point(double a,double b){}
}

但如果要在一个类中实现多个构造方法,那么就要保证构造方法的参数不同,或者是类型不同。为了解决构造方法的局限性,我们就使用工厂设计模式。
工厂设计模式就是指,用一个单独的类,再使用静态普通方法代替构造方法做的事情。

class PointFactory{
    public static Point MackXY(){
        Point p=new Point();
        .......
        return p;
    }
    public static Point MackAB(){
        Point p=new Point();
        .......
        return p;
    }
}

在构造线程池中也有多种方法:

 public static void main(String[] args) {
        //线程池是动态的,cache缓存用了之后不立即释放
        ExecutorService service= Executors.newCachedThreadPool();
        //固定创建几个线程
        ExecutorService service1=Executors.newFixedThreadPool(3);
        //相当于定时器,但不是一个扫描线程进程操作而是多个线程了
        ExecutorService service2=Executors.newScheduledThreadPool(4);
        //固定只有一个线程
        ExecutorService service3=Executors.newSingleThreadExecutor();
    }

上述多种方法都是对ThreadExecutor进行的封装,这个类非常丰富,提供了很多参数,标准库中上述多种方法实际给这个类填写了不同的参数来构造线程。
在这里插入图片描述
具体看最后一种构造方法,因为包含了前面三种

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

int corePoolSize:核心线程数
int maximumPoolSize:最大线程数
long keepAliveTime:非核心线程在终止之前等待新任务的最长时间
TimeUnit unit:时间单位
BlockingQueue workQueue:阻塞队列,存放线程池的任务
ThreadFactory threadFactory:用于创建新线程的工厂。
RejectedExecutionHandler handler:线程拒绝策略

RejectedExecutionHandler handler:线程拒绝策略
一个线程池中,能容纳的线程数目已经达到最大上限,继续再添加将有不同的效果:有以下4种效果

1.ThreadPoolExecutor.AbortPolicy:线程池直接抛出异常
2ThreadPoolExecutor.CallerRunsPolicy:新添加的任务由添加任务的线程自己执行
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最老的任务
4.ThreadPoolExecutor.DiscardPolicy:丢弃当前新加的任务

5.2实现

public class MyThreadPool {
    //设置任务队列
        BlockingDeque<Runnable> queue=new LinkedBlockingDeque<>();
   //任务放到队列
   public void submit(Runnable runnable) throws InterruptedException {
       queue.put(runnable);
   }
   //线程执行
    public MyThreadPool(int n) throws InterruptedException {
       for (int i=0;i<n;i++){
           Thread t=new Thread(()->{
               try {
                  Runnable runnable = queue.take();
                   runnable.run();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           });
           t.start();
       }
    }
}
class M{
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool=new MyThreadPool(4);
        for (int i=0;i<100;i++){
            int id=i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("i="+id);
                }
            });
        }
    }
}

6.总结

单例模式,阻塞队列,定时器,线程池,是一些常用的多线程代码,希望同学们能够熟练掌握它们得使用方法,感兴趣的同学也可以自己实现一下。

下期预告:多线程进阶

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

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

相关文章

js原生写一个小小轮播案例

先上示例&#xff1a; 附上代码 html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content…

西奥CHT-01软胶囊硬度测试仪:重塑行业标杆,引领硬度测试新纪元

西奥CHT-01软胶囊硬度测试仪&#xff1a;重塑行业标杆&#xff0c;引领硬度测试新纪元 在当今医药领域&#xff0c;软胶囊作为一种广泛应用的药品剂型&#xff0c;其品质的稳定性和安全性直接关系到患者的健康。而在确保软胶囊品质的各项指标中&#xff0c;硬度测试尤为关键。…

Bookends for Mac v15.0.2 文献书籍下载管理

Bookends Mac版可以轻松地将其导入参考 &#xff0c;并直接搜索和进口从数以百计的线上资料来源。Bookends Mac版使用内置在浏览器中下载参考与PDF格式的文件&#xff0c;或和/或网页的点击。 Bookends for Mac v15.0.2注册激活版下载 本文由 mdnice 多平台发布

云密码机的定义与特点

云密码机&#xff0c;作为云计算环境中保障数据安全的关键设备&#xff0c;其重要性不言而喻。它基于虚拟化技术&#xff0c;通过提供高性能的数据加解密、密钥管理等服务&#xff0c;确保云上数据的安全与隐私。下面&#xff0c;安策科技将从云密码机的定义、特点、应用场景以…

JAVA中的线程、死锁、异常

线程 Thread 一、程序 1&#xff0e;一段静态代码&#xff08;静态&#xff09; 二、进程 1&#xff0e;动态的&#xff0c;有开始&#xff0c;有结束&#xff1b;2&#xff0e;程序的一次执行过程&#xff0c;3&#xff0e;操作系统调度分配资源的最小单位&#xff1b; 三、…

图形渲染在AI去衣技术中的奇妙之旅

在这个数字化飞速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为了我们生活中不可或缺的一部分。它像一位神秘的魔法师&#xff0c;以其不可思议的力量改变着我们的世界。今天&#xff0c;我要和大家探讨的&#xff0c;是一个颇具争议却技术含金量极高的话…

群晖上部署农场管理系统farmOS

什么是 farmOS &#xff1f; farmOS 是一个基于 Web 的应用程序&#xff0c;用于农场管理、规划和记录保存。它由志愿者社区开发&#xff0c;旨在为农民、开发人员和研究人员提供一个标准平台。 需要注意的是&#xff0c;群晖内核版本太低会遇到下面的错误&#xff0c;这个 AH0…

2024暨南大学校赛热身赛解析

文章目录 A 突发恶疾B Uzi 的真身C 时间管理大师D 基站建设E 在仙境之外weiwandaixu 题目地址 A 突发恶疾 斐波那契数列 fn [0]*1000006fn[0],fn[1] 0,1for i in range(2,1000002):fn[i] (fn[i-1]fn[i-2])%998244353n int(input()) print(fn[n])B Uzi 的真身 分析&#xff…

SSM【Spring SpringMVC Mybatis】——Maven

目录 1、为什么使用Maven 1️⃣获取jar包 2️⃣添加jar包 3️⃣使用Maven便于解决jar包冲突及依赖问题 2、什么是Maven 3、Maven基本使用 3.1 Maven准备 3.2 Maven基本配置 3.3 Maven之Helloworld 4、Maven及Idea的相关应用 4.1 将Maven整合到IDEA中 4.2 在IDEA中新建…

使用脚本一键部署项目的示例(脚本会创建syetemctl的系统服务)

文章目录 说明使用脚本一键部署本项目开启/停止服务开启/关闭开机自动运行更新项目 参考地址&#xff1a;https://github.com/Evil0ctal/Douyin_TikTok_Download_API?tabreadme-ov-file 说明 后续相关项目可以使用这种方式创建脚本&#xff0c;脚本均放置在项目根目录下的bas…

【C++干货基地】揭秘C++STL库的魅力:stiring的初步了解和使用

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

AI大模型探索之路-训练篇17:大语言模型预训练-微调技术之QLoRA

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

动态规划——路径问题:LCR 166.珠宝的最高价值

文章目录 题目描述算法原理1.状态表示&#xff08;题目经验&#xff09;2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现CJava 题目描述 题目链接&#xff1a;LCR 166.珠宝的最高价值 算法原理 1.状态表示&#xff08;题目经验&#xff09; 对于这种路径类的问题&…

Linux中动态库的用法及优缺点?怎样制作动态库和静态库?

一、什么是gcc gcc的全称是GNU Compiler Collection&#xff0c;它是一个能够编译多种语言的编译器。最开始gcc是作为C语言的编译器&#xff08;GNU C Compiler&#xff09;&#xff0c;现在除了c语言&#xff0c;还支持C、java、Pascal等语言。gcc支持多种硬件平台. 在 Linux…

在做题中学习(52): 山脉数组的峰顶索引

852. 山脉数组的峰顶索引 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;二分查找 思路&#xff1a;O(logn)的时间复杂度&#xff0c;很可能是二分法&#xff0c;再看看有没有二段性&#xff1a; 由题目可以知道&#xff0c;i的左边比i小&#xff0c;右边比i大&am…

47.Redis学习笔记

小林coding -> 图解redis的学习笔记 文章目录 Rediswindwos安装docker安装redis启动redis使用RDM访问虚拟机中的redispython连接redis缓存穿透、击穿、雪崩基本数据类型高级数据类型高并发指标布隆过滤器分布式锁Redis 的有序集合底层为什么要用跳表&#xff0c;而不用平衡…

Spring_概述

Spring 官网Spring Framework&#xff08;Spring&#xff09;文档位置重点内容Overview 官网 Spring官网 Spring Framework&#xff08;Spring&#xff09; 文档位置 重点 IoC容器AOP&#xff1a;面向切面编程AOT&#xff1a;ahead of time&#xff0c;提前编译Web 框架&…

面试分享——订单超30分钟未支付自动取消用什么实现?如何使用Redis实现延迟队列?

目录 1.订单超时未支付自动取消&#xff0c;这个你用什么方案实现&#xff1f; 2.如何使用Redis实现延迟队列 2.1实验步骤 2.2实现生产可用的延迟队列还需关注什么 3.总结 电商场景中的问题向来很受面试官的青睐&#xff0c;因为业务场景大家都相对更熟悉&#xff0c;相关…

金仓面对面 | 人大金仓×安硕信息共话金融信用风险管理数字化转型之道

金仓面对面 在数字化浪潮的推动下&#xff0c;人大金仓携手行业先锋&#xff0c;共同开启一场关于创新与转型的思想盛宴——金仓面对面。这不仅是一场对话&#xff0c;更是一次智慧的火花碰撞&#xff0c;一次行业数字化转型洞察的深度挖掘。 行业精英汇聚&#xff1a;我们荣幸…

SSH的魅力:为何它成为远程访问的首选

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、SSH简介 2、SSH的历史与发展 3、SSH的主要用…