一、多线程描述
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进行处理,非常感谢!