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

操作系统-多线程案例

一、单例模式(是一种设计模式)

设计模式有很多种,不同的语法中也有不同的设计模式

单例 = 单个实例(对象)

某个类,在一个进程中,只应该创建出一个实例,(原则上不该有多个)使用单例模式可以对咱们的代码进行个更严格的校验和检查

实现单例模式有多种实现方法,有两种最基础的实现方式

1、饿汉模式

饿表示非常迫切,因为实例在类加载的时候就创建了,创建时机非常早,程库一启动,实例就创建了

2、懒汉模式

创建实例的时机不大一样,创建实例的时机更晚,只到第一次使用才会创建实例

首次调用getlnstane,此时 instance 引用为 null,就会进入 if 条件,从而把实例创建出来,后续再调用 getInstance ,由于 instance 不再是null,此时不会进入 if ,直接返回之前创建好的引用

该操作的执行时机并不确定,甚至可能整个程序压根用不到这些方法,就把创建的操作省下来了

饿汉模式: getInstance 直接返回 Instance 实例,该操作的本质就是读操作,多个线程同时读是线程安全的

懒汉模式:有读有写,是线程不安全的

1、该线程安全问题,本质上是读,比较,写这三个操作不是原子的,导致后一个线程读的值,前一个线程还没写(脏读),把锁加在外面才能保证读和修改是一个整体

2、代码每次 getInstance 都要加锁,但加锁只是在 new 出对象前加上很必要,对象new完以后,后续调用getinstance是读操作,不用加锁

可以给上述代码一个判定:对象未创建才加锁,创建了就不加锁了

3、指令重排序引起的线程问题,指令重排序也是编译器优化的一种方式

调整原有代码的执行顺序,保证逻辑不变的情况下提高程序的效率,解决该问题核心思路还是volatile

二、阻塞队列(基于普通队列做出的扩展,先进先出,线程安全的)

1、带有阻塞性质

若是对一个已经满了的队列进行入队列,此时入队列就会阻塞,一直到队列不满结束

若是对一个已空的队列进行出队列,此时出队列就会阻塞,一直到队列不空结束

2、基于阻塞队列,就可以实现“生产者消费者模型”

引入生产者消费者模型是为了更好做到“解耦合“

实际开发中,经常会涉及到分布式系统(服务器的功能不是由一个服务器来完成的,而是每个服务器负责一部分功能,最后通过服务器之间的网络通信,完成整个功能)

3、阻塞队列代价:需加机器,引入更多的硬件资源

1、上述阻塞队列并非是简单的数据结构,而是基于这个数据结构实现的服务器程序,又被布署到单独的主机上了(被称为消息队列message queue)

2、整个系统的结构更复杂了,要维护的服务器更多了

3、效率会降低,引入中间商还是有差价的,请求从A发送给B,这个过程要经历队列的转发,也是有一定开销的

4、削峰填谷

外界请求突发峰值,突发的峰值就会使抗压能力更弱的服务器挂掉,使用阻塞队列,即使外面的请求出现峰值,也是由队列来承受峰值请求,队列无业务逻辑,只是存储数据,抗压能力是比较强的

当请求出现谷值,队列也会照常提供给服务器请求,让服务器保证正常运转

5、Java中提供了现成的阻塞队列数据结构

ArrayBlockingQueue 基于数组实现

LinkedBlackingQueue 基于链表实现

PriorityBlackingQueue 带优先级的队列

put 方法是带阻塞的加入,offer 不带阻塞,take方法出队列也是带阻塞的

对程序的优化,归根结底增加机器、增加硬件资源是最立杆见影的做法,仅仅是软件层面的优化,投入产出比较低

实际开发中,生产者消费者模型,住往是多个生产者,消费者,这里的生产者和消费者不仅仅是一个线程,也可能是一个独立的服务器程序,其至是一组服务器程序

生产者消费者模型核心仍是阻塞队列,使用synchronized 和 wait/notify来达到线程安全和阻塞

三、实现定时器

设定一个时间,当时间到了的时候,定时器自动去执行某个逻辑

什么样的情况能的使用lambda,得是函数式接口(interface只能有这一个接口)

Timer里内置了线程(前台线程),timer 不知道代码是否会添加新的任务进来,处于严阵以待的状态,要使用 cncel 主动结束否则 timer 不知道是否其他地方要继续添加任务

定时器核心:

1、有个扫描线程,负责判定时间到/执行任务

2、还要有个数据结构保护所有被注册的任务

数据结构用优先级队列来表示(在多线程下使用,调用 schedule 是一个线程,扫描是另一个线程,要关注线程安全问题)

四、线程池

提前把要用的对象准备好,用完的对象也不要立刻释放,先留着下次备用

虽然线程成本低,但是频繁的创建和销毁开销还是很大的

1、优化方案

1、引入轻量级线程:也称为纤程/协程,协程本质是程序员在用户代码中进行调度,不是靠内核的调度器调度,节省了很多调度上的开销

2、线程池

在使用过程中并未真的频繁创建销毁,而只是从线程池里取线程,使用完了还给线程池,从线程池中取线程(纯用户代码,可控)

通过系统申清来创建线程,需要内核来完成,(不太可控)

2、标准库中的线程池 ThreadPoolExecutor

这个类,构造方法的参数,侧面映射出线程池的设计思路,需要咱们了解一下标准库提供的线程池,持有的线程个数并非是一成不变的,会根据当前任务量自适应线程数

int corePoolSize 核心线程数(线程池内最少有多少线程)

int maximumpoolSize 最大线程数(线程池内最多有多少线程)

kpepAliveTime  实习生线程空闲时间超过了这个时间阈值,就会被销毁

BlockingQueue<Runnabe>  使用Runnable作为描述任务的主体,若想任务带有优先级,则使用PriorityBlrkingQueue

ThreadFactory  线程工厂(工厂模式也是常见的设计模式)

通过静态方法封装 new 操作,在方法内部没定不同属性完成对象初始化,构造对象的过程就是工厂模式

线程工厂提供了方法,让方法封装 new Thread 操作并给 Thread 设置一些属性

3、RejectedExecutionHandler(拒绝策略)

线程池能容纳的元素是有限的,若继续添加任务会怎么样

AborPolicy:继续添加任务,直接抛出异常,新的任务未完成,旧的任务也没法做了

calerRunsPolicy: 新的任务由添加任务的线程执行

discardoldestPolicy:丢弃最老的任务

discardPolicy:丢弃最新任务(调用的线程不执行该任务,线程池也不执行)

创建线程池要没定线程池的线程数量,若一个进程中所有的线程都是cpu密集型,每个线程所有工作都是在cpu上执行的,此时线程数不该超过N(cpu核心数)

若一个进程的所有线程都是IO密集型,每个线程大部分工作都等待IO,cpu消耗很少,此时线程数可以很多,远远超过N

综上,由于程序的复杂性很难对于线程池的线程数进行估数,可通过测试的方式,尝试给线程池设定不同的线程数目分别进行能测试,衡量每种程数目下总的时间开销和系统资源占用的开销,找到二者的合适值


http://www.mrgr.cn/news/63745.html

相关文章:

  • java控制台打印减法口诀
  • 臻于智境 安全护航 亚信安全受邀出席新华三智算新品发布会
  • 聊聊Web3D 发展趋势
  • 信息论与熵information and entropy
  • 【分布式技术】分布式事务深入理解
  • Webserver(2.7)内存映射
  • 5天学习RAG路线图,你信吗?
  • 遥感图像Trento原始数据集下载
  • 如何在 Vue 页面中禁止选择、右键、复制及 F12 开发者工具
  • shell脚本编程
  • 奥数与C++小学四年级(第十八题 小球重量)
  • excel的宏1
  • 2024 年河南省高等职业教育技能大赛实施方案概述
  • 智能指针(内存泄漏问题)
  • JavaScript网页设计案例:智慧社区可视化大屏的实现
  • 如何使用和打开jconsole
  • 基于Python的黑龙江旅游景点数据分析系统【附源码】
  • Qml-Gif的显示
  • C语言结构体 变量对齐原理
  • MySQL-如果你在添加外键时忘加约束名,如何找到系统默认的约束名
  • vue2开发 对接后端(go语言)常抛异常情况以及处理方法汇总
  • 智能合约中的AI应用
  • 构造小练习
  • 最逼真的AI换脸软件,Pluse下载介绍(可直播)
  • LeetCode994. 腐烂的橘子(2024秋季每日一题 54)
  • C++ 模版(初阶)