并发编程基础知识
创建线程的三种方式#
- 继承Thread对象,重写run方法
- 实现runnable接口,作为Thread构造参数 - Thread默认的run()方法中会调用runnable对象的run()方法
- 实现callable接口,配合FutureTask
对象使用 - 底层依然是runnable接口,通过共享变量实现线程之间的控制和通信
+ View Code
操作系统中线程的生命周期#
新建(New):新创建出来的线程,尚未执行start()方法
就绪(Ready):等待操作系统分配CPU时间片
运行(Running):正在运行中的线程
阻塞(Blocked):等待锁或者等待被唤醒
终结(Terminated):执行完成的线程
JMM中线程的生命周期#
新建(New):新创建出来的线程,尚未执行start()方法
运行态(Running):操作系统中就绪态和运行态的集合,代表正在执行中的线程
阻塞(Blocked):由Synchronized触发的阻塞状态
等待(Waiting): 等待被唤醒
限时等待(Timed Waiting):带超时时间的Waiting状态
终结(Terminated):执行完成的线程
tips: LockSupport是JMM为实现锁机制提供的类,其底层调用unsafe的park/unpark()方法
interrupt 线程中断标志#
JMM底层为Thread提供了interrupt标志位,用于控制线程的执行。
- interrupt():将标志位设置为true(此时,会唤醒处于part()的线程)
- interrupted():将标志位恢复为false(使标志位复位,以等待下一次中断信号)
- isInterrupted():获取线程的中断状态
tips:interrupt标志位用于优雅地中止线程,当interrupt标志被置为true时,会唤醒对应的线程,由线程内部实现决定后续逻辑(InterruptedException)
tips:interrupt只发送中断信号,而是否中断以及具体的中断逻辑由线程自行决定
synchronized(类锁/对象锁)#
-
特性:
- 不可中断
- 可重入(底层有重入计数器)
- 非公平锁
-
JDK 6对synchronized的优化:无锁化(偏向锁,轻量级锁)
- 偏向锁:适用场景 - 在同步周期内大概率不存在并发问题,通过CAS设置偏向标志,代表已有线程获取锁
- 轻量级锁(自适应自旋锁):适用场景 - 大部分锁都会在很短时间内被释放,通过CAS自旋不断获取锁,避免线程挂起的损耗
- 重量级锁:通过Monitor对象实现锁,内部实现为EntryList和WaitSet两个阻塞队列 - 用于同步状态(锁)和等待状态(wait - notify)
死锁的条件#
- 互斥,共享资源 X 和 Y 只能被一个线程占用;
- 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
- 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
- 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
volatile#
并发编程问题:
- CPU存在多级缓存(会导致缓存一致性问题)
- 对指令实现重排序优化(编译器优化重排序/指令并行重排序/内存系统重排序),导致多线程任务下指令执行顺序的不可预测
volatile:
- 即时可见性:修饰变量发生修改,会立刻被其他线程感知(可见) - 缓存一致性协议
- 避免指令重排序:在修饰变量处插入内存屏障,避免指令重排序(实现指令的happen-before关系) - Lock字节码指令
缓存一致性协议(MESI):#
- M(Modify):缓存已被修改
- E(Exclusive):独占缓存,数据只缓存在了当前CPU缓存中,未被修改
- S(Shared):共享缓存,多个CPU均存储了该缓存,且未被修改
- I(Invalid):缓存已失效
内存屏障:#
- 读内存屏障(处理失效的缓存): volatile在读操作之前会插入一个load屏障(刷新无效缓存,得到最新的值)
- 写内存屏障(将当前缓存的值写回主存): volatile变量在写操作之后会插入一个store屏障(将之前store的缓存写入主存)
- 读写内存屏障
Happens-Before模型#
- 程序次序规则(as-if-serial语义):单线程环境下,执行结果不变
- volatile变量规则: volatile 修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作.
- 传递性规则:若a happens-before b,b happens-before c,则有 a happens-before c
- start()规则:如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
- Join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
- 等
Thread.join()#
使当前线程阻塞,等待Thread结束后被唤醒,让线程的执行结果对后续线程可见(实现线程间的顺序性)
ThreadLocal - 线程隔离存储机制#
+ View Code
tips#
- sleep(), join() 和 yiled() 的区别
- sleep: 让线程睡眠指定时间,会释放cpu时间片(单不会释放锁资源)
- join - wait()/notify(): 让主线程等待join线程的执行结果(happens-before模型join规则)
- yiled: 让出时间片,触发重新调度,效果等同于sleep(0)
- i++操作是线程安全的吗?
不是,i++底层字节码分为load,store两条指令,非原子性,可能存在并发问题
欢迎疑问、期待评论、感谢指点 -- kiqi,愿同您为友
-- 星河有灿灿,愿与之辉
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步