当前位置: 首页 > news >正文

java多线程(初阶)

1.单例模式

通过静态方法来new一个实例化对象,且构造方法被private修饰,是java中的一种设计模式,分为懒汉模式,饿汉模式。

(1)饿汉模式:

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

在类加载的时候对象就已经被new出来了,只需要通过静态方法来获取就行了。

(2)懒汉模式:

class SingletonLazy {private static SingletonLazy instance =  null;private static SingletonLazy getInstance() {if(instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() {}
}

类加载的时候对象并没有创建出来,调用静态方法以后才能够new出对象,并且返回。

(3)注意事项:

懒汉模式(多个线程修改同一个变量)比饿汉模式(多个线程读取同一个变量)更不安全。原因:

上述情况导致对象new了两次,违背了单例的要求。创建对象不一定是轻量的,背后可能是重量的。故对上述懒汉模式中的代码进行加锁。

此时创建对象的这个操作就是原子的,当有一个线程获取到这把锁,在它没有释放锁之前,其他线程只能等待这把锁被释放,从而导致阻塞。这个等待时间不知道有多长,可能会阻塞很久,所以又做了如下优化:

第一个if作用:用来判断是否要进行加锁,第二个if作用:用来判断是否要进行new对象。如果一个对象已经被new好了,另一个线程调用该方法的时候,在第一个if就进不去,就不会进行加锁,直接返回对象。

但在这个过程中可能会出现一个问题:指令重排序。

new对象的这个操作分三步:

a.内存分配地址

b.在内存空间上构造对象

c.将地址赋予instance引用

正常来说应该abc的执行顺序,而现在有可能的执行顺序时acb,如果按照这个执行顺序,有可能线程t1在还ac执行完过后,instance是一个不为空的非法对象,此时线程t2来调用这个方法了,直接就返回了instance对象(非法的),所以为了避免上述的情况,就让volatile修饰变量。

2.阻塞队列

是一种特殊的队列,线程安全且具有阻塞特性。

阻塞特性:

a.在队列满的时候,往队列中插入元素会阻塞等待;

b.在队列为空的时候,删除元素也会阻塞等待;

(1)生产者消费者模型:(典型的使用阻塞队列完成的模型)

生产者把生产出来的内容放到阻塞队列中,消费者从队列中获取内容。

(2)优点:

a.解耦合

添加了阻塞队列:

通过上述方式耦合程度大大降低。

b.削峰填谷

(3)JAVA标准库中的阻塞队列(BlockingQueue)

是一个接口,有三种实现方式:基于链表(适合:数据不要求比较,且数据量大),基于顺序表,基于优先级队列(适合:数据要求比较)

ps:阻塞队列是继承于Queue(队列)的,尽量不要使用Queue里面的方法,因为可能会造成线程不安全且没有阻塞特性。

3.定时器

客户端发起请求之后,正常情况下来说,服务器是会接收并且处理请求,然后返回响应。但是也有可能服务器挂了,客户端等待了很久都没有接收到响应。

对于客户端来说不能无限的等待下去,到达这个最大期限以后会再次发送请求,如果还是没有接收到响应,就会直接断开于服务器的连接或者做其他的事。

上述这个最大期限就是定时器所需要完成的事情。

(1)JAVA标准库中的定时器(Timer)

在java.util包下。

eg:

TimeTask是一个抽象类,实现了Runnable接口;delay(延时)单位:ms

启动程序观察,程序并没有停止。

原因;Timer中的其他线程没有执行完毕。(Timer中不仅有扫描线程还有一些其他的线程,这些线程没有结束是导致进程没有结束的主要原因)扫描线程就是Timer中执行TimeTask任务的一个线程。

(2)自定义定时器

a.Timer中需要一个线程,来扫描是否要执行任务。

b.需要一个数据结构把任务保存起来

c.还需要一个类,来描述当前的任务(任务内容和时间)

MyTimerTask类:

public class MyTimerTask{private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}public Runnable getRunnable() {return this.runnable;}public long getTime() {return this.time;}
}

MyTimer类:

public class MyTimer {//有一个数据结构来存储任务,其中任务执行顺序是按照时间来执行的private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int)(o1.getTime()-o2.getTime());}});//创建锁对象private Object locker = new Object();//schedule方法用来放任务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();if(System.currentTimeMillis() >= task.getTime()) {//执行任务task.getRunnable().run();queue.poll();}else {locker.wait(task.getTime() - System.currentTimeMillis());}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}
}

test类:

public class test {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("1000");}}, 1000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("2000");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("3000");}},3000);System.out.println("程序启动!!");}
}

程序启动:

也不会结束,由于while循环(模拟了Timer里面程序的结束结果)。

ps:notify:

4.线程池

线程是轻量级线程,但是频繁地创建线程这个开销也是不容忽视的。

有两种解决方式:

a.协程:比线程还轻量,但是在JAVA不常用

b.线程池:在创建线程1的时候,就把其他线程2,3,4,5.....给创建好了,要用的时候直接去池子中取

从池子中取要比直接创建的速率要快?

是的。因为从池子中取这个操作是一个纯用户态的操作,这个过程是可控的,而通过new来创建对像,是内核态+用户态的操作,这个过程是不可控的。(创建线程会调用相关api,并到系统内核中去执行)

(1)ThreadPoolExecutor(线程池)

java中提供了一些线程池,但本质都是对ThreadPoolExecutor的封装

a.构造方法

主要理解最后一个:

 

分别为核心线程数和最大线程数。

线程池中线程的数目是不确定的,在核心线程数和最大线程数范围之间。

举个例子来理解:

一个公司中有正式员工和实习生,所有的正式员工就相当于核心线程数,公司所有正式员工+实习生就是最大线程数。实习生不允许摸鱼,而正式员工允许,在任务较多的,做不过来就会多找几个实习生来帮助完成任务,当任务不多的时候,这时候就看哪个实习生在摸鱼的,就将他开除。

线程池中类似这样做来满足效率的需求和避免过多的系统开销。

线程活跃时间,就类似于上述允许实习生摸鱼的时间。

阻塞队列(消息队列),用来存放线程池中的任务。

这个一个工厂类,由该类来负责创建对象(通过该类中的静态方法来创建并返回),并且返回。(工厂模式的体现)

拒绝策略:当线程池中任务已经放满了过后,此时再来一个新的任务,对于不同的拒绝策略,有着不同的效果:

 关于线程池中线程的数目:

是一个不确定的值(如果答出某个确切的值都是错误的)

原因:线程中的代码一般分两种:cpu密集型和io密集型

cpu密集型:代码主要进行算术运算和逻辑运算

io密集型:代码主要涉及io操作

假设一个cpu上最多允许跑的线程数目是N个:

假设一个代码中所有都为cpu密集型,线程池中的线程数目不应该超过N个,超过的话就会把cpu吃满,影响cpu的执行速率,反而更多的线程还会增加调度的开销。

假设一个代码中所有都为io密集型,由于是对硬盘进行操作,不吃cpu,所以线程池中的线程数目可以超过N个。

所以线程池中的数目是不确定的。不同的代码,线程池中的线程数目是不同的(无法知道一个代码中是cpu密集型还是io密集型)。

(2)自定义线程池:

a.搞一个数据结构来存储任务。

b.ThreadPoolExecutor中需要创建出n个线程来执行任务(构造方法中实现)

c.需要一个submit方法来提交任务。

MyThreadPoolExecutor类:

public class MyThreadPollExecution {//阻塞队列private BlockingQueue<Runnable> queue = new ArrayBlockingQueue(100);//提交任务public void submit(Runnable runnable) throws InterruptedException {//此处应该有拒绝策略,这里省略synchronized (locket) {queue.put(runnable);locket.notify();}}private Object locket = new Object();public MyThreadPollExecution(int n) {//创建n个线程来执行任务for(int i = 0; i < n; i++) {Thread t = new Thread(()-> {try {synchronized (locket) {while (queue.isEmpty()) {locket.wait();}Runnable runnable = queue.take();runnable.run();}} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}}
}

test类:

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

执行结果:

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Godot4自学手册】第四十五节用着色器(shader)制作水中效果
  • linux C语言remove函数及相关函数
  • 如何选择较为安全的第三方依赖版本?
  • [C++][opencv]基于opencv实现photoshop算法可选颜色调整
  • 微前端架构下的应用版本回退策略与实践
  • C语言 | Leetcode C语言题解之第341题扁平化嵌套列表迭代器
  • idea付费插件,哪个比较好用?
  • 书生浦语大模型全链路开源开放体系学习
  • Docker和虚拟机的区别详细讲解
  • Android T about screen rotation(二)
  • spring boot 接收第三方mq消息
  • 基于JAVA美容院管理系统(源码+论文+讲解等)
  • Windows利用ssh免密码登录Linux
  • 应急响应-DDOS-典型案例
  • Jmeter接口测试断言详解
  • [NodeJS] 关于Buffer
  • 08.Android之View事件问题
  • 5、React组件事件详解
  • C++类中的特殊成员函数
  • GraphQL学习过程应该是这样的
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • JavaScript 基本功--面试宝典
  • Java多线程(4):使用线程池执行定时任务
  • Java基本数据类型之Number
  • js面向对象
  • k8s如何管理Pod
  • MaxCompute访问TableStore(OTS) 数据
  • Sublime Text 2/3 绑定Eclipse快捷键
  • Travix是如何部署应用程序到Kubernetes上的
  • 前端_面试
  • 前端面试之CSS3新特性
  • 全栈开发——Linux
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 一些关于Rust在2019年的思考
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • Mac 上flink的安装与启动
  • ​​​【收录 Hello 算法】9.4 小结
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (Java数据结构)ArrayList
  • (M)unity2D敌人的创建、人物属性设置,遇敌掉血
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (推荐)叮当——中文语音对话机器人
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • ../depcomp: line 571: exec: g++: not found
  • .env.development、.env.production、.env.staging
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .Net Core 笔试1
  • .net Stream篇(六)
  • .net解析传过来的xml_DOM4J解析XML文件
  • .net企业级架构实战之7——Spring.net整合Asp.net mvc
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce