锁
# 3.锁
# 前言
# 锁的公平性
公平锁:多个线程不会发生同一个线程连续多次获得锁的可能,保证了公平性
非公平锁:系统倾向于让一个线程再次获得已经持有的锁, 这种分配策略是高效的,非公平的
# 乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,操作数据时不会进行加锁,直到提交数据时才通过某种机制来验证是否存在冲突(一般会使用版本号),如果失败则要重复读-比较-写的操作。
- Java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
# 悲观锁
顾名思义。已一种悲观的态度防止一切数据冲突,即认为写多,遇到并发写的可能性高,就是说在修改数据之前把数据锁住,然后再进行读写,在释放之前任何人不能对其数据进行操作。
- Java中的悲观锁就是 Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。
# 自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。
如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。
# Java中的锁
# ReadWriteLock
- 读读共享、读写互斥、写写互斥
# Synchronized
synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。
synchronized是同步锁:同一时间内只允许一个线程访问共享数据
Synchronized 作用范围
作用于方法时,锁住的是对象的实例(this);
当作用于静态方法时,锁住的是Class实例,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
Synchronized 核心组件
1) Wait Set:哪些调用 wait 方法被阻塞的线程被放置在这里;
2) Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队中;
3) Entry List:Contention List 中那些有资格成为候选资源的线程被移动到Entry List 中;
4) OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;
5) Owner:当前已经获取到所资源的线程被称为 Owner;
6) !Owner:当前释放锁的线程。
# synchronized 和 ReentrantLock 的区别
共同点:
都是用来协调多线程对共享对象、变量的访问
都是可重入锁,同一线程可以多次获得同一个锁
都保证了可见性和互斥性
不同点:
ReentrantLock 显示的获得、释放锁,synchronized 隐式获得释放锁
ReentrantLock 可响应中断、可轮回,synchronized 是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
ReentrantLock 是 API 级别的,synchronized 是 JVM 级别的
ReentrantLock 可以实现公平锁
ReentrantLock 通过 Condition 可以绑定多个条件
底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,lock 是同步非阻塞,采用的是乐观并发策略
Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现。
synchronized 在发生异常时,会自动释放线程占有的锁 ,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。
Lock 可以让等待锁的线程响应中断 ,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
Lock 可以提高多个线程进行读操作的效率,既就是实现读写锁等。