java 锁的相关总结

java 锁的相关总结
一: 什么是锁?

  1. 锁是一种互斥的机制,在多线程环境中实现对资源的协调与控制,凡是有资源被多线程共享,涉及到修改的情况就要考虑锁的加持。

了解锁之前还需引申 操作系统用户态和内核态 的概念,为什么要用到这个概念,在后面会提到:
由于需要限制不同的程序之间的访问能力,防止他们获取别的程序的内存数据,或者获取外围设备的数据,并发送到网络,CPU划分出两个权限等级,用户态和内核态
所有用户程序都是运行在用户态的,当程序需要做一些内核态的事情,,例如从硬盘读取数据,,或者从键盘获取输入等。而唯一可以做这些事情的就是操作系统,此时程序就需要操作系统请求以程序的名义来执行这些操作,即将用户态程序切换到内核态。 注:用户态和内核态之间的切换特别消耗资源

java中最典型的synchronized
synchronized是在硬件层面依赖特殊的CPU指令。synchronized别编译后会生成monitorenter和monitorexit两个字节码指令,依赖这两个字节码指令进行线程同步。monitor,监视器(管程),一旦线程进入了monitor,那么其他线程只能等待,只有当这个线程退出,其他线程才有机会进入。monitor依赖于操作系统的Mutex Lock实现,所以每当挂起或唤醒线程,都要切换到操作系统的内核态,这个操作比较重量级。在某些情况下,甚至于切换时间本身就会超出线程执行任务的时间。java6开始,对synchronized进行了优化,引入了对象锁的4种状态,分别是无锁、偏向锁、轻量级锁、重量级锁。

二:锁的java实现方式
在java中,锁的实现主要采用两种方式:1、基于Object的悲观锁;2、基于CAS的乐观锁,Lock接口是基于CAS原理实现。java5之前的版本只有synchronized锁,基于操作系统提供的指令,在内核态实现多线程之间访问资源的同步性;之后发现基于内核态的synchronize的锁开销很大,提出了Lock锁机制,在java5版本中被官方采纳;随后java官方对synchronized进行了优化,提出了对象锁的4种状态概念。在java的后续版本中,两者在性能上差别需要根据实际情况进行选择使用。

三: 锁的分类
1)乐观锁/悲观锁

乐观锁认为每次读取数据的时候总是认为没有其他线程进行更新操作,所以不去加锁。但是在更新的时候回去对比一下原来的值,看有没有被更改过。适用于读多写少的场景。乐观锁的本质是CAS。

eg:

(1)mysql中类比version号更新 update xxx set a=aaa where id=xx and version=1

(2)java中的atomic包属于乐观锁实现,即CAS。

悲观锁在每次读取数据的时候都认为其他线程会修改数据,所以读取数据的时候也加锁,这样别人想拿的时候就会阻塞,直到这个线程释放锁,这就影响了并发性能。适合写操作比较多的场景。

eg:

(1) mysql中类比for select xxx for update; update update xx set a = aaa 案例中synchronized实现就是悲观锁(1.6之后优化为锁升级机制),悲观锁书写不当很容易影响性能。

乐观锁和悲观锁往往依靠数据库提供的锁机制实现,数据库锁才能真正保证数据访问的排他性,应用层锁无法保证外部系统不会修改数据。

2)独享锁/共享锁

独享锁是指该锁一次只能被一个线程所持有,而共享锁是指该锁可被多个线程所持有。

案例一:ReentrantLock,独享锁,基于AQS(AbstractQueuedSynchronizer),实现了公平锁和非公平锁,ReentrantLock支持可重入(单个线程执行时重新进入同一个子程序仍然是线程安全的,即一个线程可以不用释放锁而重复获取一个锁多次,只是在释放的时候也需要响应释放多次)。

ReadWriteLock,read共享,write独享
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

3)分段锁
ConcurrentHashMap线程安全的主要原理,ConcurrentHashMap中维护了一个segment数组,数组中的每个元素是HashEntry数组;segment继承ReentrantLock,每个segment对象就是一把锁,一个segment对象内部存在一个HashEntry数组,即HashEntry数组中的数据同步依赖同一把锁,不同的HashEntry数组的读写互不干扰,就形成了分段锁。

4)可重入锁
可重入锁指的获取到锁后,如果同步块内需要再次获取同一把锁的时候,直接放行,而不是等待。其意义在于防止死锁。 实现原理实现是通过为每个锁关联一个请求计数器和一个占有它的线程。如果同一个线程再次请求这个锁,计数器将递增,线程退出同步块,计数器值将递减。直到计数器为0锁被释放。 场景见于父类和子类的锁的重入(调super方法),以及多个加锁方法的嵌套调用。

5)公平锁/非公平锁
基本概念:常见于AQS,公平锁就是在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,直到按照FIFO的规则从队列中取到自己。 非公平锁与公平锁基本类似,只是在放入队列前先判断当前锁是否被线程持有。如果锁空闲,那么他可以直接抢占,而不需要判断当前队列中是否有等待线程。只有锁被占用的话,才会进入排队。

优缺点:公平锁的优点是等待锁的线程不会饿死,进入队列规规矩矩的排队,迟早会轮到。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。 非公平锁的性能要高于公平锁,因为线程有几率不阻塞直接获得锁。ReentrantLock默认使用非公平锁就是基于性能考量。但是非公平锁的缺点是可能引发队列中的线程始终拿不到锁,一直排队被饿死。

编码方式:ReentrantLock支持创建公平锁和非公平锁(默认),想要实现公平锁,使用new ReentrantLock(true)。AQS中有一个state标识锁的占用情况,一个队列存储等待线程。 state=0表示锁空闲。如果是公平锁,那就看看队列有没有线程在等,有的话不参与竞争,追加到尾部。如果是非公平锁,那就直接参与竞争,不管队列有没有等待者。 state>0表示有线程占着锁,这时候无论公平与非公平,都直接去排队。

备注: 因为ReentrantLock是可以定义公平、非公平锁次数。所以state>0而不是简单的0和1,而synchronized只能是非公平锁

6)CountDownLatch

允许一条或多条线程等待其他线程中的一组操作完成后,再继续执行;

如图所示:CountDownLatch调用次序

img

7)锁升级

java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。设计4种锁的目的是线程尽量在操作系统的用户空间完成锁的的获取与释放,一旦进入重量级锁状态,将会调用内核空间,产生较大的开销。

偏向锁(一个对象被加锁,但是在实际运行过程中,只有一个线程会获取这个对象锁,那么,此时最好的方式就是不经过系统状态切换,在用户态就完成任务,即对象的mark word标记中需要记录线程id);
轻量锁(两个线程需要获取对象锁的情况,线程通过CAS机制尝试获取锁,一旦获得,线程和对象锁绑定,并且互相知道对方的存在);
重量级锁(自旋等待的线程超过一个,轻量级锁就升级为重量级锁,对象锁的状态被标记为重量级锁,需要通过monitor来对线程进行控制,此时使用同步原语来锁定资源,对线程的控制最为严格)就是围绕如何使得cpu的占用更划算而展开的。
在操作系统中,阻塞就要存储当前线程状态,唤醒就要再恢复,这个过程是要消耗时间的。如果A使用锁的时间远远小于B被阻塞和挂起的执行时间,那么我们将B挂起阻塞就相当的不合算,于是出现自旋,自旋指的是锁已经被其他线程占用时,当前线程不会被挂起,而是在不停的试图获取锁(可以理解为不停的循环),每循环一次表示一次自旋过程。显然这种操作会消耗CPU时间,但是相比线程下文切换时间要少的时候,自旋划算。 如果自旋的线程过多,再上重量级锁阻塞和挂起。

举个例子,假设公司只有一个会议室(共享资源)

偏向锁: 前期公司只有1个团队,那么什么时候开会都能满足,就不需要预约,OA里直接默认设定为使用者A。A在会议室门口挂了 个牌子,写着A专用。
轻量级锁: 随着业务发展,扩充为2个团队,于是当AB同时需要开会时,两者在OA抢占。偏向锁升级为轻量级锁,但是未抢到者在门口会不停敲门询问(自旋,循环)。
重量级锁: 后来随着团队规模继续扩充,发现这种不停敲门的方式很烦,BCDEF……都在门口站着一直问。于是锁再次升级。 如果会议室被A占用,那么其他团队直接等着(wait进入阻塞),直到A用完。
注意点:

上面几种锁都是JVM自己内部实现,我们不需要干预,但是可以配置jvm参数开启/关闭自旋锁、偏向锁。
锁可以升级,但是不能反向降级:偏向锁→轻量级锁→重量级锁
无锁争用的时候使用偏向锁,第二个线程到了升级为轻量级锁进行竞争,更多线程时,进入重量级锁阻塞
img

8)互斥锁/读写锁

典型的互斥锁:synchronized,ReentrantLock,读写锁:ReadWriteLock 前面都用过了;
互斥锁属于独享锁,读写锁里的写锁属于独享锁,而读锁属于共享锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值