前言Synchronized和ReentrantLock锁在并发编程中比较常用,在业务开发过程中也可能会用到分布式锁。分布式锁常用的框架有基于Redis的分布式锁框架Redisson和基于Zookeeper的分布式锁框架Curator。当然还有其他的锁实现方式,这里不再介绍。这篇文章主要是学习Java锁和分布式锁源码后的一个总结。1锁的最基本元素为什么要使用锁?关于为什么要使用锁的问题,答案显而易见:“为了避免多线程并发冲突”。在多线程中修改公共数据时,必须保证只有线程在运行。这里的公共数据可以是公共变量,也可以是数据库中的一行数据。锁的基本要素知道了为什么需要加锁之后,就可以得到加锁的基本要素:加锁标志:加锁成功的标志是什么?锁持有者:哪个线程添加了锁?锁重入:自己加锁后,再锁?锁并发:并发锁失败的线程怎么办?解锁:使用后如何解锁?总之,应该是这些要素。如有遗漏,欢迎补充。2锁标志锁标志需要一个标志来表示加锁是否成功,加锁标志必须保证原子性。synchronized底层是用C++实现的,在ObjectMonitor对象中有一个_count参数来标识是否持有锁。ReentrantLock是基于AQS实现的,其中state表示线程是否持有锁。ReentrantReadWriteLock也是基于AQS实现的,其中state的高16位代表读锁,低16位代表写锁。Redisson是基于Redis的Hash数据结构实现的,其中锁key是否存在可以指示是否加锁。Curator是基于ZooKeeper临时时序节点实现的,通过判断锁路径下是否存在该节点来指示是否加锁。3.锁持有者锁持有者必须是当前线程,但是需要在分布式锁中加入一台机器,以防止服务之间的线程冲突。synchronized在ObjectMonitor对象中,_owner指的是获得锁的线程。ReentrantLock和ReentrantReadWriteLock是AQS的Node节点中的Thread对象,用来表示获取锁的线程。Redisson将UUID:ThreadId存储在Hash数据结构的field字段中,防止存在多个服务实例时的并发冲突。Curator创建的临时时序节点包含UUID,代表锁机,如/locks/lock_01/_c_UUID-lock-0000000000。4锁重入当获取锁的线程尝试再次获取锁时,保证计数。synchronized将累积_count并更新CAS。ReentrantLock会累积AQS的状态,更新CAS。Redisson使用Lua脚本来累加Hash结构的值。Curator在Java代码中维护了一个AtomicInteger类型的lockCount字段来表示可重入。5LockWaitingSynchronized并发加锁失败线程会自旋等待,涉及到偏向锁、轻量级锁、重量级锁的锁升级过程。一开始是无锁偏向锁:一段同步代码已经被一个线程访问,这个线程自动获取锁。大部分锁都是同一个线程获取的,会造成偏向锁。目的是在只有一个线程执行同步代码块时提高性能。JDK6之后,在JVM中默认开启。对象头标志(01-无锁)是否为偏向锁标志(1-为偏向锁)轻量级锁:当锁为偏向锁时,被其他线程访问,偏向锁升级为偏向锁轻量级锁,其他线程通过自旋形式尝试获取锁对象头标志位00。重量级锁:只有一个线程在等待,线程在自旋等待获取锁。当第三个线程自旋到一定次数或者持有锁1次自旋时,就会升级为重量级锁。objectheadflag10ReentrantLock会放入AQS双向同步队列中,监听上一个节点是否为虚拟头节点,如果是则尝试获取锁。非公平锁的新线程默认会参与抢锁,公平锁会先检查队列是否为空。LockSupport.park()方法将在旋转和等待时使用。这时候会释放CPU资源,其他线程会调用LockSupport.unpark()。您可以使用tryLock方法来设置时间。如果在指定时间内获取锁失败或中断,则返回锁失败。Redisson并发锁。失败的线程会获取当前锁的超时时间,然后通过SemaphoretryAcquire方法阻塞一定时间后再次尝试获取锁。公平锁会额外使用RedisList数据结构作为线程等待队列,使用sortedset有序集合数据结构存储等待线程的顺序(score为超时时间戳)。Curator的并发锁会直接创建一个临时的时序节点,然后在时序节点中监听自己的前一个节点(防止羊群效应)。默认是公平锁。6锁释放synchronized不需要手动释放。ReentrantLock将状态递减为0后,唤醒后续节点,后续节点需要调用LockSupport.unpark(s.thread)。Redisson主动释放,直接删除key。超时释放就是服务宕机了,没有看门狗续租。指定时间后,RedisKey会自动释放。Curator的主动释放将删除该节点。如果服务宕机,该节点将被自动删除。7小结本文从多个角度对锁和分布式锁的基本要素进行了总结和分析。基于MySQL等数据库的锁也可以实现,供参考。本文转载自微信公众号“程序员小航”,可通过以下二维码关注。转载本文请联系程序员小航公众号。
