锁
一、锁的状态
JDK1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁。但是在JDK1.6后,JVM为了提高锁的获取与释放效率对synchronized 进行了优化,引入了偏向锁和轻量级锁,从此以后锁的状态就有了四种:无锁、偏向锁、轻量级锁、重量级锁。并且synchronized 锁的四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,这四种锁的级别由低到高依次是:无锁 < 偏向锁 < 轻量级锁 < 重量级锁。
偏向锁:偏向于第一个获得锁的线程。synchronized 默认开启偏向锁。
轻量级锁(自旋锁):自旋锁就是让不满足条件的线程循环等待,而不是立即挂起。自旋锁默认4秒后开启。
锁升级的过程:
二、锁的优缺点
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 | 适用于只有一个线程访问同步块场景。 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度。 | 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 | 追求响应时间。同步块执行速度非常快。 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU。 | 线程阻塞,响应时间缓慢。 | 追求吞吐量。同步块执行速度较长。 |
三、锁的分类
1. 线程会不会锁住同步资源:乐观锁(提交更新时才检测)读多写少 效率高 与 悲观锁(修改数据之前把数据锁住)
synchronized和实现了lock接口的锁都是悲观锁。
悲观锁:synchronized、ReentrantLock、ReenTranReadWriteLock
乐观锁:CAS、StampedLock
2. 根据加锁的粒度:类级锁、对象锁、分段锁和单个变量/字段加锁
2.1 类级锁:synchronized
2.2 对象锁:synchronized
2.3 分段锁:ConcurrentHashMap:jdk 1.8使用了Node + cas + synchronized实现了加锁。
2.4 单个变量/字段加锁:使用CAS和java中原子引用类,通过保证编发编程中原子性,实现数据安全同步更新。
3. 根据锁的兼容性:独占锁与共享锁
独占锁:synchronized、ReentrantLock、ReenTranReadWriteLock中的WriteLock
共享锁:ReenTranReadWriteLock中的ReadLock
独占锁:也叫排它锁,是指该锁一次只能被一个线程所持有。如果别的线程想要获取锁,只有等到持有锁线程释放
获得排它锁的线程即能读数据又能修改数据,与之对立的就是共享锁。
共享锁:共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁
获得共享锁的线程只能读数据,不能修改数据。
4. 多个资源竞争时要不要排队:公平锁(排队)与非公平锁(不排队)
公平锁:ReentrantLock、ReenTranReadWriteLock可公平可不公平
非公平锁:synchronized
5. 一个线程中多个流程能不能获得同一把锁:重入锁与不可重入锁
重入锁:synchronized、reentrantLock。是指同一个线程可以重入上锁的代码段,不同的线程进入则需要进行阻塞等待。
不可重入锁:是指同一个线程不可以重入上锁后的代码段。
6. 锁同步资源失败是否需要阻塞:自旋锁(不阻塞)与阻塞锁(阻塞)
自旋锁:轻量级锁
阻塞锁:synchronized
参考:1. java锁分类 2. Java 可重入锁和不可重入锁的区别
四、JUC并发包中常用类(java.util.concurrent)
1..JUC的atomic包中AtomicBoolean、AtomicInteger、AtomicReference等原子变量类。运用了CAS
2.1 AtomicInteger、AtomicLong和AtomicBoolean
2.2 JDK1.8:LongAdder、DoubleAdder 分段锁 效率高
2.JUC的locks包中的AbstractQueuedSynchronizer(AQS)、ReentantLock(显式锁)、ReentrantReadWriteLock等类。
3.JUC下的一些同步工具类(tools):CountDownLatch(闭锁)、Semaphore(信号量)、CyclicBarrier(栅栏)等。
4.JUC下的一些并发容器类(collections):ConcurrentHashMap、CopyOnWriteArrayList等。
5.JUC下的一些阻塞队列实现类(collections):ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue等。
6.JUC下的一些Executor框架的相关类(executor): 线程池的工厂类->Executors 线程池的实现类->ThreadPoolExecutor/ForkJoinPool。
五、常用锁
1.synchronized与ReentrantLock的区别
1)用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用于代码块。
2)获取锁和释放锁的机制不同:synchronized 是自动加锁和释放锁的,而 ReentrantLock 需要手动加锁和释放锁。
3)锁类型不同:synchronized 是非公平锁,而 ReentrantLock 默认为非公平锁,也可以手动指定为公平锁。
4)响应中断不同:ReentrantLock 可以响应中断,解决死锁的问题,而 synchronized 不能响应中断。
5)底层实现不同:synchronized 是 JVM 层面通过监视器(Monitor)实现的,而 ReentrantLock 是基于 AQS 实现的。
2.ReentrantLock、ReentrantReadWriteLock 和StampedLock的区别
锁 | 特性 | 是否支持重入 | 是否支持锁升级 | 适合场景 |
---|---|---|---|---|
ReentrantLock | 独占可重入 | 是 | 无 | 纯写入 |
ReentrantReadWriteLock | 非独占可重读,读写锁,悲观锁 | 是 | 否 | 读写均衡 |
StampedLock | 非独占不可重入,多模式锁,乐观锁 | 否 | 是 | 读多写少 |
3.CountDownLatch(闭锁)、CyclicBarrier(栅栏)和Semaphore(信号量)的区别?JUC下的一些同步工具类(tools
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
2)CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
3)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。