多线程
2021-02-14 21:36:19 26 举报
AI智能生成
多线程相关知识
作者其他创作
大纲/内容
概念
进程
线程
NEW
新建状态,线程被构建,还未调用start()方法
RUNNABLE
运行状态,jvm把就绪和运行中统称为RUNNABLE
BLOCKED
阻塞状态,表示线程阻塞于锁
WAITING
等待状态,调用了wait()方法,需要其他线程调用niotfy()或者notifyAll()方法来激活
TIMED_WAITING
超时等待状态,不同于WAITING,可以在超过指定时间后,自行激活
TERMINATED
终止状态,表示该线程已经执行完毕
并发与并行
并发
同一段时间,多个任务都在执行
并行
同一时刻,多个任务同时执行
背景
为什么用多线程
- 随着处理器的核心数不断增多,单线程程序已无法发挥多核处理器的优势
- 线程比进程切换效率更高
引入问题
内存泄漏
静态集合类
数据库连接、网络连接、IO连接等
变量不合理的作用域
内部类引用外部类
死锁
死锁发生条件
1.互斥条件:该资源任一时刻只能被一个线程拥有
2.请求与保持条件:一个线程在获取资源阻塞时,对已获得的资源不释放
3.不剥夺条件:一个线程获得的资源在其使用完成之前不能被其他线程主动剥夺,只能由自己主动释放
4.循环等待条件:多个线程之间形成首尾相接的循环等待资源关系
避免死锁
只需要破坏四个条件中的任意一个即可
- 互斥条件:本来就是让线程之间互斥,所以无法破坏
- 请求与保持条件:一次性申请所有的资源
- 不剥夺条件:可以让线程在无法获得资源时,先把已获得资源进行释放
- 循环等待条件:按照某一顺序来获取资源
解决死锁
上下文切换
一般情况下线程数都比cpu核心数要多,cpu只能不断在线程之间切换来执行每一个线程。cpu每次切换都得保存当前状态,和加载下一个线程的状态。这就是上下文切换
线程同步
synchronized
作用范围
方法
静态方法
使用的同步锁是类对象,和其他静态同步方法存在锁竞争
非静态方法
使用的锁是当前对象,同一对象之间存在锁竞争,不同对象之间不存在竞争关系
代码块
使用的锁是synchronized中传入的对象,依据是否同一对象而决定否存在竞争
底层原理
方法
代码块
每个java对象的对象头中都有monitor对象,通过能否获取到monitor对象来判断能否获取锁
锁优化
自旋锁
当线程竞争锁失败时,不直接阻塞自己,而是自旋(空等待,比如一个有限的for循环)一会儿,在自旋的同时重新竞争锁。如果在自旋结束前获得了锁,那么获取锁成功;否则自旋结束后阻塞自己
自旋锁可以减少线程阻塞造成的线程切换(包括挂起线程和恢复线程)
- 单核处理器情况下,不存在实际的并行。当前线程不阻塞自己,拥有锁的线程就无法执行,锁永远不会释放。进而,如果线程多而处理器少,自旋也会造成不少无谓的浪费
- 自旋锁要占用CPU,如果是计算密集型任务,这一优化通常得不偿失,减少锁的使用是更好的选择
- 如果锁竞争时间比较长,那么自旋通常不能获得锁,白白浪费自旋占用的CPU时间。这通常发生在所持有时间长,且竞争激烈的场景中,此时应主动禁用自旋锁
使用-XX:-UseSpinning参数关闭自旋锁优化;-XX:PreBlockSpin参数修改默认的自旋次数。
偏向锁
偏向锁的目标是减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗
如果有其他线程申请锁,那么偏向锁很快就膨胀为轻量级锁。浪费了维持偏向锁的性能消耗
使用参数-XX:-UseBiasedLocking禁止偏向锁优化(默认打开)。
轻量级锁
使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功。否则可以继续使用自旋锁获取或者升级为重量级锁
轻量级锁的目标是减少无实际竞争的情况下,使用重量级锁产生的性能消耗
如果锁竞争激烈,轻量级锁很快就将升级为重量级锁,那么维持轻量级锁的过程就成了浪费
重量级锁
内置锁在Java中被抽象成监视器锁(monitor)。监视器锁直接对应底层操作系统的互斥量(mutex)
这种同步方式成本非常高,包括系统调用引起的内核态和用户态切换、线程阻塞造成的线程切换等
synchronized与ReentrantLock
两者都是可重入锁
synchronized依赖于JVM,ReentrantLock依赖于API
- synchronized是非公平锁
- ReentrantLock可指定是公平锁或非公平锁
ReentrantLock比synchronized多一些高级功能
等待可中断
可实现公平锁
可实现选择性通知
synchronized与volatile
并发编程的三个特性
原子性
一个操作或者多次操作,要么都执行、要不都不执行。synchronized可以保证原子性
可见性
当一个线程对共享变量进行了修改,其他线程可以马上看到最新的值。volatile可以保证可见性
有序性
代码在执行过程中,编译器会对字节码进行冲重排序优化,无法保证代码的执行顺序就是编写代码的顺序。volatile可以禁止指令重排
volatile是synchronized的轻量级实现
多线程访问volatile关键字不会阻塞,synchronized可能会阻塞
volatile能保证数据的可见性,不能保证原子性;synchronized两者都可以保证
volatile主要解决多线程之间数据的可见性问题;synchronized解决多线程之间数据的同步性问题
ThreadLocal
每个线程的专属变量,不会被其他线程访问到
ThreadLocal中有一个静态内部类ThreadLocalMap,可以理解为ThreadLocal的定制化HashMap实现。KEY为线程对象,Value为set进去的值
内存泄漏问题
ThreadLocalMap中的Key为ThreadLocal的弱引用,而Value是强引用。所以在ThreadLocal没有被外部强引用的时候,在垃圾回收时会把Key给回收掉,而Value却不会被回收。这样会出来Key为null的Entry。如果我们不做什么措施,Value就永远无法被回收,发生内存泄漏。ThreadLocal已经考虑了这种情况,在调用set()、get()和remove()方法时,会清理掉Key为null的Entry
0 条评论
下一页