【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;//后续中既会读又会改,为了避免内存可见性+volatileprivate 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() {@Overridepublic void run() {System.out.println("3000");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000");}},2000);timer.schedule(new TimerTask() {@Overridepublic 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;}@Overridepublic 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() {@Overridepublic void run() {System.out.println("3000");}},3000);myTimer.schedule(new Runnable() {@Overridepublic 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() {@Overridepublic void run() {System.out.println("i="+id);}});}}
}

6.总结

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

下期预告:多线程进阶

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

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

相关文章

支付宝小程序如何去除页面下拉回弹

描述&#xff1a;支付宝小程序页面下拉时会产生回弹&#xff0c;如果页面上有拖拽功能&#xff0c;会有影响 解决方法&#xff1a; 页面xx.config.js中设置&#xff1a;allowsBounceVertical: “NO” 官方文档&#xff1a;https://opensupport.alipay.com/support/FAQ/7110b5d…

WT32-ETH01作为TCP Client进行通讯

目录 模块简介WT32-ETH01作为TCP Client设置电脑作为TCP Server设置连接并进行通讯总结 模块简介 WT32-ETH01网关主要功能特点: 采用双核Xtensa⑧32-bit LX6 MCU.集成SPI flash 32Mbit\ SRAM 520KB 支持TCP Server. TCP Client, UDP Server. UDP Client工作模式 支持串口、wif…

鸿蒙OpenHarmony技术:【Docker编译环境】

Docker环境介绍 OpenHarmony为开发者提供了两种Docker环境&#xff0c;以帮助开发者快速完成复杂的开发环境准备工作。两种Docker环境及适用场景如下&#xff1a; 独立Docker环境&#xff1a;适用于直接基于Ubuntu、Windows操作系统平台进行版本编译的场景。基于HPM的Docker环…

uni-app+vue3 +uni.connectSocket 使用websocket

前言 最近在uni-appvue3websocket实现聊天功能&#xff0c;在使用websocket还是遇到很多问题 这次因为是app手机应用&#xff0c;就没有使用websocket对象&#xff0c;使用的是uni-app的uni.connectSocket 为了方便测试这次用的是node.js一个简单的dom&#xff0c;来联调模拟…

Apache Flume Agent内部原理

Apache Flume Agent内部原理 Apache Flume 是一个可扩展的、分布式的日志收集、聚合和传输系统。在 Flume 中&#xff0c;Agent 是一个独立的进程&#xff0c;负责接收、传输和处理数据。Agent 内部包含多个组件&#xff0c;每个组件都有不同的功能和责任。 1. Source&#xff…

三下乡社会实践投稿攻略在这里

在当今信息爆炸的时代&#xff0c;如何让自己的声音被更多人听到&#xff0c;成为许多人和企业所关心的问题。其中&#xff0c;向各大媒体网站投稿&#xff0c;成为了一种常见的宣传方式。但是&#xff0c;如何投稿各大媒体网站&#xff1f;新闻媒体发文策略又有哪些呢&#xf…

光耦推荐—高速风筒方案中用到哪些光耦型号

高速风筒是现代生活中常见的电器设备&#xff0c;广泛应用于家庭、商业和工业领域&#xff1b;光耦是一种能够将输入信号转换成输出信号的元器件&#xff0c;其作用在于将电气信号转换成光信号&#xff0c;从而实现电路的隔离和保护&#xff1b;采用光耦可实现对风机转速和温度…

【管理咨询宝藏99】离散制造智能工厂战略规划方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏99】离散制造智能工厂战略规划方案 【格式】PDF版本 【关键词】智能制造、先进制造业转型、数字化转型 【核心观点】 - 推进EHS、品质一致性、生…

Failed to start tomcat.service: Unit is not loaded properly: Bad message 如何解决?

错误 “Failed to start tomcat.service: Unit is not loaded properly: Bad message” 通常意味着的 tomcat.service systemd 配置文件存在语法错误或配置不正确。为了解决这个问题&#xff0c;一步步检查和修正这个服务文件。 1. 检查 tomcat.service 文件 首先&#xff0c…

CSS文字描边,文字间隔,div自定义形状切割

clip-path: polygon( 0 0, 68% 0, 100% 32%, 100% 100%, 0 100% );//这里切割出来是少一角的正方形 letter-spacing: 1vw; //文字间隔 -webkit-text-stroke: 1px #fff; //文字描边1px uniapp微信小程序顶部导航栏设置透明&#xff0c;下拉改变透明度 onP…

SQL注入(sqli-labs第一关)

sqli-labs第一关 方法一&#xff1a;手工注入 来到第一关&#xff0c;图上说我们需要一个数字的参数 于是我们先手工注入?id1 and 11 跟?id1 and 12发现页面没有报错 每张截图上面页面中有select查询语句&#xff0c;这是我在第一关的源码中加上了echo "$sql ";…

SSM【Spring SpringMVC Mybatis】——Mybatis(二)

如果对一些基础理论感兴趣可以看这一期&#x1f447; SSM【Spring SpringMVC Mybatis】——Mybatis 目录 1、Mybatis中参数传递问题 1.1 单个普通参数 1.2 多个普通参数 1.3 命名参数 1.4 POJO参数 1.5 Map参数 1.6 Collection|List|Array等参数 2、Mybatis参数传递【#与…

STL——stack容器【栈】

stack基本概念&#xff1a; 概念&#xff1a; 是一种先进后出的数据结构&#xff0c;它只有一个出口 因为只有一端可以调用&#xff0c;所以栈不支持遍历操作 栈的操作&#xff1a; 栈中进入数据称为&#xff1a;入栈(push) 栈中弹出数据称为&#xff1a;出栈(pop) 生活中…

C#编程模式之享元模式

创作背景&#xff1a;各位朋友&#xff0c;我们继续学习C#的编程模式&#xff0c;本文主要介绍享元模式。享元模式是一种结构型设计模式&#xff0c;它主要用于减少创建对象的数量&#xff0c;从而提高程序性能。它通过共享对象的方式来减少内存的使用&#xff0c;特别是系统中…

【多客陪玩】陪玩小程序源码APP+小程序+公众号开发 -源码交付,线下可爆改家政,整理师等功能

简述 随着电竞行业的快速发展&#xff0c;电竞陪玩APP正在逐渐成为用户在休闲娱乐时的首选。为了吸引用户和提高用户体验&#xff0c;电竞陪玩APP开发需要定制一些特色功能&#xff0c;并通过合适的盈利模式来获得收益。本文将为您介绍电竞陪玩APP开发需要定制的特色功能以及常…

深入学习指针3

目录 前言 1.二级指针 2.指针数组 3.指针数组模拟二维数组 前言 Hello,小伙伴们我又来了&#xff0c;上期我们讲到了数组名的理解&#xff0c;指针与数组的关系等知识&#xff0c;那今天我们就继续深入到学习指针域数组的练联系&#xff0c;如果喜欢作者菌生产的内容还望不…

Python GraphQL服务器实现库之tartiflette使用详解

概要 Tartiflette是一个为Python编写的GraphQL服务器实现,它建立在现代异步编程库如asyncio之上,提供了高性能的GraphQL执行环境。Tartiflette专注于提供最佳的开发者体验,支持最新的GraphQL特性。 安装 安装Tartiflette相对简单,但需要依赖于一些系统级的库。 首先,需…

ORACLE ODA一体机存储节点电源故障的分析处理

近期&#xff0c;某用户的ORACLE ODA一体机在例行机房巡检时出现亮黄灯告警&#xff1b;用户反馈次问题后我们立刻通过远程方式&#xff0c;登陆ODA的控制台进行查看&#xff1b; 对于ODA一体机&#xff08;2个计算节点1个存储节点&#xff09;&#xff0c;计算节点可以通过il…

Python爬虫实战:爬取【某旅游交通出行类网站中国内热门景点】的评论数据,使用Re、BeautifulSoup与Xpath三种方式解析数据,代码完整

一、分析爬取网页&#xff1a; 1、网址 https://travel.qunar.com/2、 打开网站&#xff0c;找到要爬取的网页 https://travel.qunar.com/p-cs299979-chongqing进来之后&#xff0c;找到评论界面&#xff0c;如下所示&#xff1a;在这里我选择驴友点评数据爬取点击【驴友点评…

腾讯共享WiFi项目的加盟方式有哪些?

在这个互联互通的时代&#xff0c;共享经济的浪潮正以前所未有的力量席卷全球&#xff0c;而腾讯作为中国互联网巨头之一自然不会错过这场盛宴。其推出的腾讯共享WiFi项目自问世以来就备受瞩目&#xff0c;它不仅为用户提供便捷的上网服务&#xff0c;更为创业者打开了一个全新…