多线程-使用大全 基础使用 / 锁 / 线程池 / 原子类 / 并发包 / CAS / AQS (2022版)

一、多线程描述

1、什么是cpu

CPU的中文名称是中央处理器,是进行逻辑运算用的主要由运算器、控制器、寄存器三部分组成,

  • 运算器:从字面意思看就是运算就是起着运算的作用,
  • 控制器:就是负责发出cpu每条指令所需要的信息,
  • 寄存器:就是保存运算或者指令的一些临时文件,这样可以保证更高的速度。

我们的线程运行在cpu之上的

2、什么进程/线程

一个cpu 可以同时启动多个进程, 一个进程可以插件多个线程

  • 进程: 比如我们使用电脑打开 qq, 就是一个进程, 在打开微信, 又会开启一个新的进程
  • 线程: 比如我们使用qq 开启了两个窗口和两个人同时聊天,那么对应的就开启了两个线程,如果在一个窗口来回切换联系人,那么就是一个线程

3、为什么要在进程中使用线程

一个进程想当于一个应用程序, 一个程序同一时间想做多件事,就需要多线程技术,

  • 例1:好比一台电脑想要开启多个不同的程序,就需要用到进程技术
  • 例2:妳想同时开启2个窗口和不同的人聊天,那么就要使用多线程了, 否则妳想同时和两个人聊天,只能在同一个窗口中来回切换不同的联系人

4、为什么要使用多线程

顾名思义,提高效率

  • 现实举例:如果我今天有几个任务,打扫卫生,做饭, 洗衣,我一个人来做 (1个线程) 每个任务需要一个小时,如果我找两个人 3个人来做 (3个线程),一人负责一个任务,那么我们 1个小时就能做完, 效率就直接提高了 3 倍
  • 程序举例:我们在做一个登录操作,会处理 登录用户信息检测(1秒), 用户登录日志记录(1秒), 用户登录后发送邮件/短信等提示(1秒),在单线程的情况下,用户正常登录操作需要花3秒的时间,如果使用多线程技术后, 日志+ 发送邮件/短信放到其他线程处理,那么程序执行完登录用户信息检测账 密码等没问题 1秒 时间就直接登录成功了

5、多线程一定提高效率吗/h3>

不一定,需要了解cpu调度的算法,多个线程会进行上下文切换,从该线程执行切换到另外的线程 该线程—运行切换为就绪状态, 等待调度

如果在单核的cpu之上开启了多线程,底层执行并不是真正意义上的多线程。
所以,电脑一个好的的cpu 很重要,就想人的大脑,越聪明越好

CPU调度:

  • 1.单核的cpu上每次只能够执行一次线程,如果在单核的cpu上开启了多线程,则会发生对每个线程轮流执行 。
  • 2.Cpu每次单个计算的时间成为一个cpu时间片,实际只有几十毫秒人为感觉好像是
    在多线程。
  • 3.对于线程来说,存在等待cpu调度的时候 该线程的状态是为就绪状态,如果被cpu调度则该线程的状态为运行状态
  • 4.当cpu转让执行其他的线程时,则该线程有变为就绪状态。

大致公式参考:

  • 对于CPU密集型计算任务(如:视频剪辑),线程数 = CPU核心数 + 1
  • 对于I/O密集型计算任务 (如:访问外接设备,rpc/socket), 线程数 = 2 * CPU核心数
  • 对于普通任务(如:业务逻辑),线程数 = N(CPU核心数) * (1 + WT(线程等待时间) / ST(线程时间运行时间))

6、多线程的应用场景

  • 1.异步发送短信/发送邮件
  • 2.将执行比较耗时的代码改用多线程异步执行,提高接口响应速度
  • 3.异步处理日志 日志框架底层
  • 4.异步处理消息 mq 底层

二、多线程创建

1、多线程基础创建方式

1.1、继承 Thread

1.2、实现 Runnable接口

1.3、直接 nwe(匿名内部类)

1.4、使用 lambda表达式

1.5、使用 Callable和 Future

1.6、使用线程池例如用 Executor框架

1.7、spring @Async异步注解

实现流程:

  • 1、使用AOP 拦截 @Async 注解,如: @Around(value = “@annotation(com.mayikt.service.annotation.Async )”)
  • 2、使用线程池执行被拦截的方法就实现了

三、线程安全

1、什么是线程安全问题

当多个线程同时访问一个属性,如商品数量,车票等,如果处理不当,可能导致超买情况

现实生活中相当于只有一个杯子,但是有两个人同时往这个杯子里倒一杯水的容量,肯定装不下

2、然后保证线程安全

上锁:

  • lock 锁( aqs+cas)
  • synchronized 锁 (c 语言)
  • 使用 Threadlocal (局部变量)
  • 原子类/ CAS
  • 分布式锁等

但被锁住后多线程相当于变成了单线程执行

生活实例: 杯子的水只能有一个人来倒水,另外一个来倒时发现水是满的,不需要倒了

3、什么是死锁/h3>

两个线程两把锁

  • a线程 方法 需要 b线程的锁,但b线程在等待a线程释放
  • b线程 方法 需要 a线程的锁,但a线程在等待b线程释放

现实举例:

  • a 有 b 的房门钥匙,但 a 把 b 的钥匙放在房间里了,需要等 b 打开 a 的房门
  • b 有 a 的房门钥匙,但 b 把 a 的钥匙放在房间里了,需要等 a 打开 b 的房门

结果就是,a 和 b 家里都有对方的钥匙,但都放家里了,现在都进不去

2、api

  • start():调用start()方法会使得该线程开始执行,正确启动线程的方式。
  • wait():进入等待状态,释放资源,让出CPU。需要在同步快中调用。
  • sleep():进入超时等待,不释放资源,让出CPU
  • stop():线程停止,线程不安全,不释放锁导致死锁,过时。
  • join(): 等待线程执行完成, 等于线程变为同步,它可以使得线程之间的并行执行变为串行执行。底层使用 wait 实现
  • yield():主动释放执行权,让出CPU资源重新所有线程重新强占,也有可能立刻重新获得资源执行, 注意 Oracle为Linux提供的java虚拟机中,线程的优先级将被忽略
  • notify():在锁池随机唤醒一个线程。需要在同步快中调用。
  • notifyAll():唤醒锁池里所有的线程。需要在同步快中调用。
  • Interrupt(): 线程终止

3、守护线程与用户线程

  • 1. 守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
  • 2. -默认,用户线程是独立存在的,不会因为其他用户线程退出而退出。

java 的一般业务场景下,都是使用用户线程,即独立存在的线程

五、lock 锁

1、lock 锁使用

2、lock 下 Condition 处理线程等待/释放

使用方法: 在 a 线程的 synchronized 锁中执行 让其进入等待状态, 在b 线程中使用 来唤醒a线程,让a线程正常执行

六、synchronized 锁

1、synchronized 锁使用

1.修饰代码块

2.修饰实例方法 (作用于当前实例, this 方法锁)

3.修饰静态方法 (作用于当前类, 当前类.class 锁)

2、等待 wait() 与释放 notify()

等待/通知的作用于 synchronized 锁中, 其相关方法是任意Java对象都具备的, 因为这些方法被定义在所有对象的超类java.lang.Object上

方法如下:

  • 1、notify() :释放 ,通知一个在对象上等待的线程,使其从main()方法返回,而返回的前提是该线程获取到了对象的锁
  • 2、notifyAll():释放全部, 通知所有等待在该对象的线程
  • 3、wait(): 等待/阻塞, 调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会释放对象的锁 。

使用方法: 在 a 线程的 synchronized 锁中执行 让其进入等待状态, 在b 线程中使用 来唤醒线程a,让a线程正常执行

3、Synchronized 锁的升级过程

Synchronized 锁的升级过程为 偏向锁 –> 轻量级锁 –> 重量级锁

七、并发编程1(CAS)

1、什么是并发编程/h3>

Java并发编程 相当于 多线程的框架, jdk 自带了很多包和类库

2、什么是 cas /h3>

CAS 说明:
CAS 全称 Compare and Swap,翻译成比较并交换。 执行函数CAS(V,E,N), 底层基于unsafe类实现
CAS有3个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。

CAS 实现:

  • V: 当前全局共享变量值 原子类中 value值 ===0
  • E: 旧的预期值
  • N: 修改的新值

当 E=V 时修改新的值为N,如果 E != V 将循环自旋重新获取直到 E=V 时在进行修改,可参考 compareAndSet 方法的使用

优缺点:
优点: 无锁, 使用自旋控制乐观锁, 保证原子性,线程安全
缺点:自旋 会消耗的cpu的资源。可能导致死循环/ 空转/ cpu飙高问题

3、对象在 jvm 的存储方式

为什么要先说到 jvm的对象,因为 原子类基于 实现, 底层基于 实现, 中存在一个 偏移量和锁相关信息, 也就是数据存储在jvm的位置信息

  • 对象头:其主要包括两部分数据:Mark Word、Class对象指针。特别地对于数组对象而言,其还包括了数组长度数据。在64位的HotSpot虚拟机下,Mark Word占8个字节,其记录了Hash Code、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等 ;而Class对象指针则指向该实例的Class对象,在开启指针压缩的情况下占用4个字节,否则占8个字节;如果其是一个数组对象,则还需要4个字节用于记录数组长度信息。这里列出64位HotSpot虚拟机Mark Word的具体含义,以供参考。需要注意的是在下图的Mark Word中,左侧为高字节,右侧为低字节,
  • 实例数据:用于存放该对象的实例数据
  • 内存填充:64位的HotSpot要求Java对象地址按8字节对齐,即每个对象所占内存的字节数必须是8字节的整数倍。因此Java对象需要通过内存填充来满足对齐要求

使用方式示例

5、unsafe 模拟 cas 锁

cas 底层基于 unsafe 实现

Unsafe类说明
Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。
Unsafe类是”final”的,不允许继承。且构造函数是private的

Unsafe对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得 java9已经移除Unsafe,是基于 cpu硬件支撑的

反射获取 unsafe 模拟实现 cas 锁

6、cas 实现 lock 锁

public class MayiktLock {    /*** 默认锁的状态是为=0  如果获取到锁  则锁的状态就是为1*/    private AtomicInteger lockState = new AtomicInteger(0);    private Thread cuLockThread;    /*** 获取锁*/    public void lock() {for (; ; ) {    if (lockState.compareAndSet(0, 1)) {

                                                        

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年2月19日
下一篇 2022年2月19日

相关推荐