【JUC】并发编程(二)

临界区·

一个代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

竞态条件

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

Synchronized对象锁

语法
Synchronized(对象) {
临界区
}

锁在对象方法上锁的是this对象
锁在static方法上锁的是class对象

变量的线程安全分析

成员变量与静态变量是否线程安全?

  1. 如果它们没有被共享,则安全
  2. 如果它们被共享了,根据他们的状态能否改变,分为两种情况
    ○1如果只有读操作,安全
    ○2如果有读写操作,则这段代码是临界区,要考虑线程安全

局部变量是否线程安全?

  1. 局部变量是线程安全的
  2. 局部变量引用的对象未必
    ○1如果该对象没有逃离方法的作用访问,则安全
    ○2如果对象逃离方法的作用访问,需要考虑线程安全

常见线程安全类
String、Integer、StringBuffer、Random、Vector、Hashtable、 java.util.concurrent 包下的类

Moniter

1.当线程运行临界区中加锁的代码时,锁的对象会先与Moniter关联
2.刚开始Moniter中Owner为null
3.当Thread-2执行synchronized(obj)就会将Moniter的所有者Owner设为Thread-2.Moniter中只能有一个Owner
4.在Thread-2上锁过程中,如果Thread-3、Thread-4、Thread-5也来执行synchronized(obj),就会进入entrylist BLOCKED
5.Thread执行完同步代码块的内容,唤醒EntryList中等待的线程来竞争锁,竞争非公平
在这里插入图片描述

轻量级锁

如果一个对象虽然有多线程访问,但多线程访问时间是错开的,那么可以用轻量级锁来优化
Synchronized优先进行轻量级锁加锁,加锁失败则会用重量级锁

创建锁记录对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的MarkWord
在这里插入图片描述
让锁记录中Object referernce指向对象,并尝试替换Object的MarkWord,将MarkWord的值储存入锁记录
在这里插入图片描述
如果替换成功,对象头中存储了锁记录地址和状态,表示由该线程给对象加锁
在这里插入图片描述
如果失败,有两种情况

  1. 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
  2. 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数
    在这里插入图片描述
    当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
    在这里插入图片描述
    当退出synchronized 代码块(解锁时)锁记录的值不为null,这时使用 cas 将 Mark Word 的值恢复给对象头
    成功,则解锁成功
    失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀
如果在尝试加轻量级锁过程中,操作无法成功,这时一种情况是其它线程为此对象加上了轻量级锁,这时要进行锁膨胀,将轻量级锁变为重量级锁

加锁失败,进入锁膨胀流程
为Object对象申请Moniter锁,让Object指向重量级锁,未能获得锁的线程进入Moniter的EntryList BLOCKED
在这里插入图片描述

自旋优化

重量级锁竞争失败,先进行自旋优化而不是直接进入BLOCKED,阻塞要进行上下文切换,耗费性能,自旋能减少上下文切换

偏向优化

轻量级锁在没有竞争时,每次重入仍要进行CAS操作
偏向锁第一次使用CAS时就将线程ID设置到对象头的MaekWord中,之后发现这个线程ID是自己的,就不会重新CAS,以后只要不发生竞争,这个对象就归该线程所有
在这里插入图片描述
对象头中偏向锁后三位为101
程序运行时偏向锁默认开启
调用hasjcode()会禁用掉偏向锁,因为hashcode会占31位,对象头中会替换掉偏向状态

批量重偏向

当将thread1的偏向锁重偏向为thread2次数到达阈值后(默认20),会进行批量重偏向

批量撤销

当撤销偏向锁阈值超过40次后,jvm会觉得偏向错了,不该偏向,于是整个类的所有对象都变为不可偏向,新建的对象也不可偏向

锁消除

//控制是否加锁消除优化
-XX:-EliminateLocks

wait/notify 原理

  1. owner线程发现条件不满足,调用wait方法,即可进入waitset变为waiting状态
  2. BLOCKED和WATING的线程都会处于阻塞状态,不占用CPU时间片
  3. BLOCKED线程会在owner线程释放锁时被唤醒
  4. WATING线程在owner线程调用notify或notifyall时唤醒,但唤醒后并不立即获得锁,仍需进入EntryList重新竞争
    在这里插入图片描述
    wait会使线程进入waitset中等待
    必须获得了锁才能进入所属的waitset
    waitset中可以有多个线程等待)

sleep(long n)与wait(long n)区别

  1. Wait是对象方法,sleep是线程方法
  2. Wait需要和synchronized配合,sleep不需要和synchronized配合
  3. Wait时释放对象锁,sleep不会释放对象锁

park/unpark

unpark既可以在park之前调用,也可以在park之后调用,都可以恢复线程运行

park/unpark与wait/notify区别

  1. Wait,notify/notifyall必须配合object moniter一起使用,而park,unpark不用
  2. Park\unpark 是以线程为单位来阻塞和唤醒线程,而notify只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,没有那么精确
  3. Park/unpark可以先unpark,而wair/notify不能先notify
    在这里插入图片描述
  4. 当前线程调用 Unsafe.park() 方法
  5. 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁
  6. 线程进入 _cond 条件变量阻塞
  7. 设置 _counter = 0
    在这里插入图片描述
  8. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  9. 唤醒 _cond 条件变量中的 Thread_0
  10. Thread_0 恢复运行
  11. 设置 _counter 为 0
    在这里插入图片描述
  12. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  13. 当前线程调用 Unsafe.park() 方法
  14. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
  15. 设置 _counter 为 0

死锁

一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程获得A对象锁,接下来想获取B对象的锁
t2 线程获得B对象锁,接下来想获取A对象的锁

活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束
可增加随机睡眠时间,避免活锁的产生

饥饿

一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束

ReentrantLock

相对于synchronized它具备以下特点

  1. 可中断
  2. 可以设置超时时间
  3. 可以设置为公平锁,默认为不公平(公平锁,按进入阻塞队列的顺序,先进入,先获得锁)
  4. 支持多个条件变量

基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可打断
竞争未能获得到锁,进入EntryList阻塞状态可以用interrupt打断,打断后会抛出异常
lock.lockInterruptibly(); //设置可打断

锁超时
lock.trylock //返回值为boolean类型
例:lock.trylock(1,Timeunit.SECONDS)

条件变量
synchronized中也有条件变量,就是waitset,当条件不满足时进入waitset
ReentrantLock支持多个条件变量(Condition),好比synchronized只有一件休息室,而ReentrantLock支持多间休息室,唤醒也是按休息室来唤醒

用法
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值