JUC——线程安全
目录
unpark可以在park之前调用,也可以在park之后调用编辑
1.线程安全问题
1.1Java线程安全问题体现
两个线程对初始值为0的静态变量一个做自增,一个做自减,结果是0吗?
/** * JMM之原子性问题,由于上下文切换导致的指令交错进行,也就是!原子性问题 */ // 共享资源 static int count; public static void main(String[] args) throws InterruptedException { // 会产生原子性问题的情况, 本质: 多线程指令交错执行 Thread t1 = new Thread(() -> { for (int i = 0; i < 5000; i++) { count++; } },"t1"); // t1线程的解决: /*Thread t1 = new Thread(() -> { synchronized ("abc") { for (int i = 0; i < 5000; i++) { count++; } } },"t1");*/ Thread t2 = new Thread(() -> { for (int i = 0; i < 5000; i++) { count--; } },"t2"); // t2的解决版: /*Thread t2 = new Thread(() -> { synchronized ("abc") { for (int i = 0; i < 5000; i++) { count--; } } },"t1"); */ t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count);上诉代码会有原子性问题!
Java内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换:
可能出现错误情况:
临界区
一段代码块内如果存在对共享资源的多线程读写操作,则称这段代码块为临界区!
竞态条件
2.针对原子性问题的解决方案
为了避免出现临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized, Lock。
- 非阻塞式:原子变量,concurrent包下的变量的一些api
2.1互斥和同步的区别
互斥: 指不同线程不能同时对共享资源进行操作,不同线程之间是互斥的.必须当前线程执行结束
才能别的线程来执行.
同步: 线程排队执行,两个线程之间也不能同时操作.但是是按照某种先后次序来运行,依赖于要执行的任务同步是更加复杂的互斥,是按照某种先后次序来运行的...
2.2synchronized关键字
2.3synchronized底层原理
2.31Java对象头
以32位虚拟机为例
普通对象
数组对象
其中MarkWord结构为
2.3.2Monitor(锁)
Monitor被翻译为监视器或管程
每个 Java 对象都可以关联一个 Monitor 对象,Monitor 也是 class,其实例存储在堆中,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针,这就是重量级锁
Monitor对象是操作系统提供的一个对象!Monitor结构如下:
2.4synchronized原理之字节码
getstatic #2 拿到lock锁对象 引用地址
dup 复制一份
astore_1 lock引用 ->slot 1(存储到)解锁时用
执行synchronized monittorenter 将lock对象MarkWord置为Monitor指针
count++;
aload_1 拿到临时对象 找到lock引用
monitorexit 将lock对象MarkWord重置,唤醒EntryList
hashcode 分带年龄存储在monitor中
2.5synchronized进阶原理
2.5.1synchronized优化原理_轻量级锁
轻量级锁是对重量级锁的一种优化,是对象结构对象头中锁的一种状态。轻量级锁的使用一定是处于不存在多个线程竞争下
的场景.首先轻量级锁能提升同步性能的依据是"对于绝大部分的锁,在一个同步周期内是不存在线程竞争的!".所以一旦出现
两条或者以上的线程竞争同一把锁的情况,轻量级锁会进行锁膨胀升级为重量级锁.将锁对象Mark Word中锁标志位从00
转变为10. 如果不存在竞争的话,轻量级锁便通过CAS操作避免了使用互斥量而消耗的性能..
代码进入到同步代码块后,先看对象的锁标志位状态.如果是无锁状态,虚拟机会现在当前栈帧中创建一个Lock Record锁记录 的空间,该空间用于存储对象头中的Mark Word的拷贝..然后虚拟机常用CAS操作.将共享对象的对象头中Mark Word中前30 个bit替换成指向锁记录的指针.如果操作成功,说明当前线程获取到了对象的锁.并将Mark Word中锁标识位的01变为00.也就将 锁对象和线程绑定在一起了.如果操作失败了,说明一定有存在竞争该锁对象的其他的线程,会先检查Mark Word中指向的是否是当前线程栈帧中的锁记录,如果是说明是当前线程获取到了锁,就直接执行代码块.如果不是说明当前对象锁是被其他线程获取到了. 此时就会进行锁膨胀升级为重量级锁.且与其竞争的线程进入阻塞状态!
注意可能出现锁重入现象!当前线程又对同一个对象尝试加锁!锁重入会再创建一个锁记录作为重入的计数!
CAS操作尝试将锁对象的Mark Word恢复回去!!!
![]()
锁膨胀
如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种清空就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
当Thread-1进行轻量级加锁时,thread-0已经对该对象加了轻量级锁
这时thread-1加轻量级锁失败,进入锁膨胀流程
即为Object对象申请Monitor锁,让Object指向重量级锁地址
然后自己进入Monitor的EntryList BLOCKED
当Thread-0退出同步块解锁时,使用cas将MarkWord的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owenr为null,唤醒EntryList中BLOCKED线程。
线程会先在自己帧栈中创建lockrecord,将对象markword拷贝到自己Lock Record中。随后线程通过CAS操作尝试将对象的markword更新为指像自身的Lock Record的指针。
3.自旋优化
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
自旋要使用cpu,自旋优化选择多核cpu情况下。
自旋重试几次失败就要进入阻塞状态
4.偏向锁
轻量级锁在没有竞争时(就自己这个线程,每次重入仍然需要执行CAS操作。
Java6中引入偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的MarkWord(MarkWord不存储重量级锁的指针,不存储锁记录的地址了,存线程ID,加锁发现线程ID是自己的)头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有。
MarkWord中biased_lock中表示是否启用偏向锁
即使释放锁markword还是会记录线程id
1.撤销-调用对象hashCode
调用hashcode就会禁用偏向锁,因为Markword偏向锁存储线程id要占用54个字节,hashcode会占用31个字节
2.撤销——其它线程使用对象
t1线程使用对象,偏向锁,t1使用完之后,t2来使用偏向锁升级为轻量级锁。
3.撤销——调用wait/notify
wait/notify只有重量级锁才能使用
批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的ThreadID
当撤销偏向锁阈值超过20次后,jvm会觉得,我是不是偏向错了呢,于是会给这现象对象加锁时重新偏向至加锁线程。
批量撤销。
当撤销偏向锁阈值超过40次后,jvm会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变成不可偏向的,新建的对象也是不可偏向的。
性能消耗——轻量级锁
CAS操作
2.25锁消除
JIT 即时编译器 ,对其中热点代码进行优化 分析局部遍历不肯能逃出作用范围,就会进行锁消除
2.6变量的线程安全分析
数组,对象是堆中的,不是线程安全的;
成员变量安全问题:
错误分析:
定义一个静态变量ArrayList,局部变量中定义ArrayList引用ArrayList();
2.7常见线程安全类:
String
Integer
StringBuffer
Vector
HashTable
java.util.concurrent包下的类
线程安全类方法的组合不是线程安全的
二
String,Integer这些类只能读不能改,线程安全
2.8wait notify
为什么需要wait?——————这里可以联想到Redis的多路复用
2.8.1原理介绍之wait/notify
- Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
- BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
- BLOCKED线程会在Owner线程释放锁时唤醒
- WAITING线程会在Owner线程调用notify或notify All时唤醒,但唤醒后并不意味着立刻获得锁,仍需要进入EntryList重新竞争
2.8.2API介绍
IllegalMonitorStateException
它们都是线程之间协作的手段,属于Object对象的方法,必须要获得对象锁才能使用该API
2.9wait notify正确使用姿势
sleep(long n) 和 wait(long n)的区别
方法,使用,释放锁
注意: 关于notify()时,可能会唤醒WaitSet中不想唤醒的线程. ====> 虚假唤醒的问题!
notify()是按照JVM规范随机唤醒,而hotspot虚拟机规定队列式唤醒!1.防止虚假唤醒。
生产者把队列元素添加满了之后,进入休眠状态,三个线程生产者
一个线程消费者消费一个元素后唤醒线程,唤醒的线程接着上次wait()继续执行明明当前线程条件还未满足,但还是会往wait()方法下面的代码块中
去执行! 这就是虚假等待问题造成的未满足条件却继续向下执行!所以要使用while来判断,而不是if()2.多个线程唤醒的情况
while循环能确保线程在被唤醒后重新验证等待条件,有效处理虚假唤醒和多线程竞争的场景,保证线程协作的正确性。
2.10.保护性暂停模式
即Guarded Suspension,用在一个线程等待另一个线程的执行结果
要点
场景引用:
GuardedObject对象
结合wait()notify的正确姿势使用!
本质: 通过GuardedObject对象来获取到两个线程通信之间的数据!
增加超时功能
原理之join
是一种让线程同步的方法.让线程ab从并行执行,变为串行执行
- join()方法底层是调用了join(0),底层调用了wait(0); 要一直等待,要线程执行完之后主线程才会继续...
- join(timeout)方法底层调用wait(delay),内部是一个while循环,每次来判断delay(还需要等待的时间),来决定是否还等待...
在main()线程中调用t1.join()
底层在t1线程没有执行结束的时候,对主线程采用了等待唤醒机制,底层实际上调用的wait() 方法,直到join的线程执行结束后被notify()[由JVM来唤醒]join()底层使用了wait()方法来实现线程同步,join()方法的内部实现是保护性暂停模式的体现!
区别:
- - 保护性暂停模式是一个线程等待另一个线程的执行结果
- - join()是一个线程等待另一个线程的结束!
3。Park_Unpark使用
特点
unpark可以在park之前调用,也可以在park之后调用
原理:
每个线程都会有一个Parker对象,由三部分组成
3.1重新理解线程状态转换
一. WAITING <---> RUNNABLE
情况二
4.多把锁
4.1多把互不相干的锁
将锁的粒度细分
- 好处,是可以增强并发度
- 坏处,如果一个线程需要同时获得多把锁,容易发生死锁。
5.活跃性
5.1死锁
处于死锁的线程全部都处于BLOCK状态,都在entryList中等待了.
5.2定位死锁
检测死锁可以使用jconsole工具,或者使用jps定位进程id,在使用jstack定位死锁;
5.3活锁
两个线程互相改变对方线程的结束条件,导致谁也无法结束!
如何避免活锁??
- 给发生活锁的线程添加一些随机睡眠时间,保证一个线程先执行结束...
- ReentrantLock
6.ReentrantLock
相对于synchronized它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量(支持多个waitSets)
与synchronized一样,都支持可重入
基本语法
可重入
可打断
注意: 这里可打断是调用一个可打断的api,调用.lock()的线程和synchronized的在等待的时候都不可以倍打断
如果想让当前线程在等待的过程中可以被其他线程调用interrupt()方法打断的话,在加锁的时候通过 lock.lockInterruptibly()方法来加锁..
这个api如果当前线程处于等待阻塞状态,允许其他线程来使用interrupt()来打断..对interrupt()的复习:
1.场景: (1)可以打断处于阻塞状态(sleep wait join)的线程 注意无法打断等待synchronized的线程...
(2)打断正在运行的线程(程序继续运行..打断标记为true)锁超时
主动的方式避免死等的一种手段...
等待多久还没获取到锁就不等了,执行别的事情了!
调用lock.tryLock()的api,tryLock()可以带参数,是等待时间...
思想: 避免无限制的等待,避免死锁.
条件变量
总结
更多推荐









































































所有评论(0)