Lock 和 synchronized
前言
Java多线程编程中,保证线程安全通常会使用到synchronized
和Lock
。那么合适该使用synchronized
,什么时候该使用Lock
呢。这个就需要我们对synchronized
和Lock
有个清晰的了解。
Lock
和synchronized
synchronized
是JAVA提供的强制原子性的内置锁机制。一个synchronized
有两部分:锁对象的引用 (synchronized
方法的锁,就是该方法所在对象本身),以及这个锁保护的代码块。每个Java对象都可以作为一个用于同步的锁的角色,这些内置的锁被成为内部锁,线程进入 synchronized
块之前会自动获得锁,退出、报错异常、时会释放锁。内部锁是一种互斥锁,这就是说,至多只有一个线程可以获得锁,所以被 synchronized
声明的方法或代码块至多只有一个线程可以进入。从而保证了线程安全。
大致有如下用法:
1 | //作用于方法 |
Lock
接口定义了一些抽象了锁操作,与内部锁机制不同,它提供了更加灵活的, 无条件的,可轮训的,定时的,可中断的锁获取操作。 Lock
的接口定义如下:
1 | /* |
大致用法为:
1 | Lock lock = ...; |
从 Lock
接口的定义中可以看出,**Lock
和 synchronized
的区别:**
Lock
需要自己释放获得的锁(遗忘这个会容易导致错误,且不易排查)Lock
支持可中断的锁获取操作Lock
支持可定时的和可轮训的锁请求(由trylock
方法实现),与无条件的锁获取想比,它具有更完善的错误恢复机制。Lock
支持非结构块的锁,可以在不同的程序块中操作锁。
ReentranLock
另外,我们有比较大的机会接触到 Lock
接口的实现类 ReentranLock
,ReentranLock
支持公平性锁和非公平性锁(默认),这个选择是通过构造函数传入一个 boolean
类型的参数决定的,其内部是通过继承了AbstractQueuedSynchronizer
实现了一个FairSync
和一个NonFairSync
来实现的,这个可以在以后讲AQS的时候再深入讲解(大家也可以自行去阅读 java.util.concurrent
下的各个接口和类,相信会大有收获)。ReentranLock
也是一个标准的互斥锁:一次最多只有一个线程能够持有相同的ReentranLock
。ReentranLock
提供了和synchronized
相同的互斥和内存可见性。所以通常会将 ReentranLock
和 synchronized
放在一起对比。
可能的面试问题场景如下:
面试官: 有哪几种实现同步的方式? 面试者:
synchronized
和ReentranLock
面试官: 那synchronized
和ReentranLock
有什么区别 面试者: 。。。
其实 ReentranLock
和 synchronized
的区别和Lock
和 synchronized
的区别是一致的,只不过ReentranLock
多了个公平队列的支持!
ReentranLock
和 synchronized
的选择
ReentranLock
是 Lock
接口的实现,所以其具有 Lock
接口的特性。一般来说,不要ReentranLock
和 synchronized
混合使用,容易造成混淆。并且我们更倾向于使用简洁的内部锁机制synchronized
。只有在需要使用ReentranLock
的时候才应该使用。即:
当你需要 可定时的,可寻轮的与可中断的锁获取操作,公平队列,或者非块结构的锁 这些特性的时候才应该使用
ReentranLock
。 否则最好还是使用synchronized
。