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

❣博主主页: 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.mfbz.cn/a/601604.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux常用名命令

Linux是一款免费的操作系统&#xff0c;用户可以通过网络或其他途径免费获得&#xff0c;并可以任意修改源代码&#xff0c;这是其他操作系统做不到的&#xff0c;Ubuntu&#xff0c;Centos。 linux中&#xff0c;一切皆文件。 一些重要的目录 / 根目录&#xff0c;所有文件都放…

2024-05-08 精神分析-对损失和挫败的强烈易感性-分析

摘要: 对损失的强烈的易感性&#xff0c;会在遭受损失或者挫败的时候&#xff0c;表现的极其敏感&#xff0c;这个过程主要是在创业的过程中更加强烈的表现并带来巨大的影响。必须要对其进行彻底的分析&#xff0c;并保持对此行为的长期的警惕。 所谓前事不忘后事之师&#x…

JAVA IO/NIO 知识点总结

一、常见 IO 模型简介 1. 阻塞IO模型 最传统的一种IO模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后&#xff0c;内核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线…

WIFI模块UDP电脑端调试

一&#xff0c;两端都是电脑端 1&#xff0c;电脑本机的IP地址 192.168.137.1 2&#xff0c;新建两个不同的连接&#xff0c;注意端口 二&#xff0c;WIFI 模块和电脑端连接 1&#xff0c;设置模块端目标IP和端口&#xff0c;电脑端只接收数据的话&#xff0c;IP、端口可随…

effective python学习笔记_pythonic思维

优缺点 书的好处是很多新特性提高了可读性代码性等各方面性能&#xff0c;缺点是新特性和py老版本不兼容&#xff0c;老版本可能没有这些新特性&#xff0c;如果用了py早期版本&#xff0c;需要考虑替代方案 查py版本 import sys sys.version sys.version_info 遵循PEP8 …

python turtle

名字动画 #SquareSpiral1.py import turtle t turtle.Pen() turtle.bgcolor("black")my_nameturtle.textinput("输入你的姓名","你的名字&#xff1f;") colors["red","yellow","purple","blue"] for…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑热动态的综合能源系统碳排放流建模与分析》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

浅谈智能电气火灾监控系统的设计及应用

摘要&#xff1a;致电气火灾的原因是多方面的&#xff0c;主要成因包括漏电、绝缘层老化、短路、电火花密集、接地发生故障、电气设备自然、接触不良和电流超负荷等。文章分析电气火灾的成因&#xff0c;并探索电气火灾监控系统的设计方案与注意事项。 关键词&#xff1a;电气…

【数据结构】闲谈A股实时交易的数据结构-队列

今天有点忙&#xff0c;特意早起&#xff0c;要不先写点什么。看到个股的红红绿绿&#xff0c; 突然兴起&#xff0c;要不写篇文章分析下A股交易的简易版数据结构。 在A股实时股票交易系统中&#xff0c;按照个人理解&#xff0c;大致会用队列来完成整个交易。队列&#xff08;…

WordPress原创插件:当日24小时发布文章标题变红

WordPress原创插件&#xff1a;当日24小时发布文章标题变红 <?php// 添加自定义样式 function title_red_plugin_styles() {$current_time time();$post_time get_the_time(U);$time_difference $current_time - $post_time;if ($time_difference < 86400) {echo&l…

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

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

每日OJ题_贪心算法三③_力扣45. 跳跃游戏 II(dp解法+贪心解法)

目录 力扣45. 跳跃游戏 II 解析代码1_动态规划 解析代码2_贪心 力扣45. 跳跃游戏 II 45. 跳跃游戏 II 难度 中等 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 num…

课程作业管理系统,基于 SpringBoot+Vue+MySQL 开发的前后端分离的课程作业管理系统设计实现

目录 一. 前言 二. 功能模块 2.1. 管理员功能模块 2.2. 教师功能模块 2.3. 学生功能模块 三. 部分代码实现 四. 源码下载 一. 前言 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势…

Java并发编程: Synchronized锁升级

文章目录 一、jdk8 markword实现表二、使用工具来查看锁升级 一、jdk8 markword实现表 new -> 偏向锁 -> 轻量级锁&#xff08;自旋锁、自适应自旋锁&#xff09;-> 重量级锁&#xff1a; 偏向锁和轻量级锁都是用户空间完成的。重量级锁是需要向内核申请的。 synchr…

Jenkins +配置邮件 centos8.5 安装部署 运维系列一

1 jenkins的war包下载地址: Download and deploy 2 xftp 等方式上传到服务器 #安装jdk tar zxvf jdk-11.0.8_linux-x64_bin.tar.gz mv jdk-11.0.8/ /usr/local/jdk vim /etc/profile export JAVA_HOME/usr/local/jdk export PATH$JAVA_HOME/bin:$PATH CLASSPATH.:$JAVA_…

【Qt QML】ComboBox组件

ComboBox 是一个组合的按钮和弹出列表。它提供了一种以最小的屏幕空间呈现选项列表给用户的方式。ComboBox 使用数据模型填充。数据模型通常是一个 JavaScript 数组、一个 ListModel 或一个整数&#xff0c;但也支持其他类型的数据模型。 下面是一个简单的使用方式。 import …

odoo实施之各种导航设计

odoo各种基础能力&#xff1a;活动、讨论 玩转odoo&#xff0c;真有玩的体验 odoo消息提醒能力 odoo 讨论模块 odoo 通过new message触发任务 安装odoo studio进行拖拉拽设计 查阅官方文档&#xff0c;向官方提issue 欧洲和美国&#xff0c;虽然都是英语&#xff0c;但日期格式…

【数据结构与算法】力扣 102. 二叉树的层序遍历

题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a; root [3,9,20,null,null,15,7] 输出&#xff1a; [[3],[9,20],[15,7]]示例 2&#x…

kubeflow简单记录

kubeflow 13.7k star 1、Training Operator 包括PytorchJob和XGboostJob&#xff0c;支持部署pytorch的分布式训练 2、KFServing快捷的部署推理服务 3、Jupyter Notebook 基于Web的交互式工具 4、Katib做超参数优化 5、Pipeline 基于Argo Workflow提供机器学习流程的创建、编排…

Web前端一套全部清晰 ⑥ day4 CSS.2 复合选择器、CSS特性、背景属性、标签的显示模式

别人的议论&#xff0c;那是别人的&#xff0c;你的人生&#xff0c;才是你的 —— 24.5.7 一、复合选择器 定义&#xff1a;由两个或多个基础选择器&#xff0c;通过不同的方式组合而成 作用&#xff1a;更准确、更高效的选择目标元素&#xff08;标签&#xff09; 1.后代选择…
最新文章