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。