快速导航
浅谈 synchronized、ReentrantLock、ReentrantReadWriteLock 线程同步锁的区别
synchronized 特点
- synchronized 锁的是对象
- 使用 synchronized 能够保证在同一时刻只能有一个线程执行该段代码
- 多个线程同时调用同一个对象的同步方法和非同步方法,同步方法和非同步方法是可以同时异步执行的。同时调用同一个对象的多个同步方法则一次只能执行一个
- 同步不具有继承性。就算子类非同步方法里调用父类的同步方法,子类的非同步方法还是会被多个线程同时获取
- 被修饰的代码出现异常,锁自动释放
- 它是非公平、悲观、独享、互斥、可重入 锁
- synchronized 关键字可以保证共享变量的可见性、有序性,但无法保证原子性,所以共享变量的自增自减,需要结合 Atomic 原子类使用
- 比起同步方法,同步代码块是更好的选择,因为它不会锁住整个对象(当然它也能做到锁住整个对象)
- synchronized 底层是通过 monitor 对象来完成的
- 其实 wait() notify() 方法也依赖于 monitor 对象,所以它们才只能在 synchronized 里调用
ReentrantLock 特点
- ReentrantLock 比 synchronized 更加灵活。在扩展功能上更强大,比如具有嗅探锁定、多路分支通知等功能
- ReentantLock 除了能完成 synchronized 所能完成的所有工作外。还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法
- ReentantLock 的 lock 只能同时被一个线程占用。当一个线程执行 lock.lock() 后,其他线程会进入 lock 对象中的等待队列,从而达到同步效果
- ReentantLock 支持公平锁(是相对公平的,不是绝对公平)。默认为非公平锁,可以指定是公平或非公平,公平锁就是谁排队时间最长谁先执行获取锁
- ReentantLock 可以结合 Condition 接口实现有条件的精准唤醒
- Synchronized 结合 wait/notify/notifyAll 只能随机唤醒一个线程或者全部唤醒
- ReentantLock 提供了能够中断等待锁的线程的机制
- ReentantLock 必须手动释放锁
- ReentantLock 使用时最标准用法是在 try 之前调用 lock 方法,在 finally 代码块释放锁
- newCondition() 其实就是创建一个条件队列。所谓条件队列就是创建一个新的 ConditionObject 对象,这个对象的数据结构包含首、尾两个节点字段,每当调用 await() 方法时就会在对应的 Condition 对象中排队等待,signal() 就是唤醒条件队列中的第一个节点
ReentrantReadWriteLock 特点
- 读锁不支持 Condition 条件,否则会 UnsupportedOperationExcetpion 异常。写锁支持 Condition 条件
- 读锁不能升级为写锁,写锁可以降级为读锁
- ReentrantReadWriteLock 的公平和非公平是一种机制(不是手动设置的)
- 读锁没有公平和非公平性
- 写操作时,由于写操作可能立即获取到锁,所以会推迟一个或多个读操作或者写操作
- 读锁、写锁都支持获取锁期间被中断
- 读锁支持多个读线程进入临界区,写锁是互斥的
- 读锁:当一个读线程获取锁后,其他读线程仍然可以获取锁,但是写线程不能获取锁
- 写锁:当一个写线程拥有锁后,其他读写线程不能获取锁
- 口诀:读读共享、读写互斥、写写互斥