锁
关于java中锁的有关知识,例如有锁的种类、乐观锁和悲观锁的定义、synchronized和lock的特点、公平锁和非公平锁、读写锁ReadWriteLock、自旋锁、jvm对锁的优化
锁的分类
java中按照特定可以将锁划分为6个特性:
- 偏向锁/轻量级锁/重量级锁
- 可重入锁/不可重入锁
- 共享锁/非共享锁
- 悲观锁/乐观锁
- 自旋锁/非自旋锁
- 可中断锁/不可中断锁
偏向锁/轻量级锁/重量级锁
这三种都是描述synchronized持有的锁,synchronized锁,底层是通过持有共享对象的moniter来实现的,在持有对象的对象头中用mark word的标记,来记录当前对象是持有那种锁。
偏向锁
偏向锁:当共享对象没有发生线程竞争时,执行线程持有锁就是偏向锁。此时共享对象的对象头将持有线程的ID记录下来,再次访问该对象时,如果是执行线程就直接获取,如果不是执行线程该锁会升级为轻量级锁;
轻量级锁
轻量级锁:当共享对象存在资源竞争时,如果之前的锁是偏向锁的状态就会发生锁的升级,升级为轻量级锁。轻量级锁采用CAS和自旋的机制,避免了线程切换所带来的开销
重量级锁
重量级锁:重量级锁是利用操作系统的同步机制实现的,线程会等待和唤醒开销比较大。
可重入锁和不可重入锁
可重入锁指的是当前持有锁的线程,再次请求持有锁的时候可以直接获得锁;
不可重入锁指定是当前持有锁的线程,再次请求持有锁的时候也不能直接获得锁,需要排队;
ReentrantLock就是可重入锁
共享锁/非共享锁
共享锁指的是一个锁可以被多个线程获得;非共享锁又叫独占锁,只能被一个线程使用。
ReadWriteLock中的读锁就是共享锁,write就是独占锁。
公平锁/非公平锁
公平锁和非公平锁指的是线程获取锁是否要按照FIFO的规则
乐观锁/悲观锁
乐观锁:不独占资源,利用CAS思想来完成操作
悲观锁:先独占资源,在进行操作
自旋锁/非自旋锁
自旋锁:不出让cpu,通过空转不停的去获取资源
非自旋锁:尝试获取锁失败,就进入等待
可中断锁/不可中断锁
可中断锁:可响应线程中断的的锁
不可中断锁:必须等待执行线程释放锁,才能响应中断操作
synchronized底层的实现方法
synchronized修饰代码块时,是通过monitorenter、monitorexit指令来实现的
monitorenter指令的执行过程是
- 如果执行线程尝试获取对象的monitor的计数值是0,那么就加1,标记该线程持有锁对象
- 如果该线程已经拥有该对象,那么monitor的技术值在加1
- 如果该线程尝试获取对象的monitor不为0,且执行线程不为自己,那么等待monitor为0后在尝试获取
monitorexit
- 将锁对象的monitor计数减1操作
synchronized修饰方法时,是通过ACC_SYNCHRONIZED标记位来实现的,ACC_SYNCHRONIZED标记位标记的方法会在尝试获取到monitor锁后才执行,执行完毕后就释放。
RLock接口的常用方法
方法 | 功能 |
---|---|
lock() | 直接获取锁 |
tryLock() | 尝试获取锁,返回值表示获取成功还是失败 |
tryLock(time) | 带等待时间的tryLock() |
lockInterruptibly() | 可响应中断的锁 |
unlock() | 释放锁 |
newCondition() | 获取执行线程的Condition对象 |
公平锁和非公平锁
非公平锁存在的意义是,当前cpu执行线程可以直接获取到锁,不用参与线程等待队列,从而减少线程挂起和唤醒消耗的时间。有可能线程已经执行完成后,等待队列中的线程才进行唤起。
- ReentrantLock默认就是非公平锁
读写锁
- ReadWriteLock
特点
要么是一个线程或多个线程占用了读锁,要么是一个线程占用了写锁。两者不会同时出现
private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
private static final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
ReentrantReadWriteLock是ReadWrite接口的一个具体实现,通过readLock()获取读锁,通过writeLock()可以获取写锁
在获取读写锁的时候,可以多个线程同时获取到readLock锁,但是在获取写锁的时候只能单个写获取到写锁并且该锁只能独占在使用到写锁的时候
获取锁的方式和读写锁升降级策略
公平的读写锁时,都不允许插队,读锁和写锁都要按照等待队列的顺序依次进行排队在获取锁
非公平锁的时候?读锁在等待队列中的第一个线程不是竞争写锁的时候可以允许插队,但是第一个是竞争写锁的线程时不允许插队
写锁竞争时,允许插队,插队如果失败的话进入等待队列中
锁的升降级策略:在同一个线程中可以先获取到写锁并且不释放,然后获取到读锁,再释放写锁,从而实现线程持有锁的降级,从写锁降级为读锁。
注意锁的降级是在同一线程中执行的,并不是在不同的线程中实现的。
这样做的优点在于,写锁只独占修改的那一段时间,降级为读锁后其他线程都可以获取到读锁从而提高效率。
自旋锁
自旋锁产生的原因是,由于线程独占执行的代码时间较短,比挂起和唤醒线程的时间要短很多,因此线程通过循环判断竞争锁的条件不断的尝试获取锁,从而提高性能。
如果临界区很大,线程执行任务耗时需要很久的话,这种方式反而不利于性能
适用于并发度不是很高,并且临界区比较短小的情况这样。
JVM对锁的优化
1.自适应锁的自旋
2.锁消除 对于不会有竞争的场景,会自动消除锁
3.锁粗化 锁的范围会放大,减少加锁解锁的步骤,从而提高效率
4.偏向锁/轻量级锁/重量级锁