Java中除了synchronized外,jdk还提供了Lock来实现共享互斥,Lock实现提供比synchronized方法和语句更广泛更灵活的锁定操作,而且还可以支持多个相关联的对象,本文就来介绍一下Lock。
首先还是通过源码大致了解一下Lock是个什么东西:
public interface Lock{
void lock();
void lockInterruptibly();
boolean tryLock();
boolean tryLock(long arg0 , TimeUnit arg1) throws InterruptedException;
void unlock();
Condition newCondition();
}
根据注释挨个了解一下这些方法:
1、lock()
尝试去获取锁,如果锁暂时被其他线程持有,则会处于休眠状态,直到当前线程获得锁。
因为需要在执行完临界区之后释放锁,所以典型用法是这样的:
Lock lock = ...;
try{
// 执行临界区
}
finally{
lock.unlock();//放在finally块,确保锁的释放
}
2、lockInterruptibly
尝试去获取锁,如果锁暂时被其他线程持有,则会处于休眠状态,直到当前线程获得锁或者被其他线程interrupt()。
3、tryLock()
尝试获取锁,立刻拿到锁返回true,没拿到立刻返回false,也就是这样的获取锁的方式不等待。
典型用法如下:
Lock lock = ...;
if(lock.tryLock()){
try{
//执行临界区
}
finally{
lock.unlock();
}
}
else{
//执行没有获取锁的其他操作
}
4、tryLock(long time, TimeUnit unit) throws InterruptedException
尝试获取锁,如果锁可用,立刻拿到锁返回true,否则进入休眠状态,直到当前线程拿到锁或者被其他线程interrupt()或者过去了指定时间长度。
当锁获取到则返回true,否则返回false。
5、unlock()
释放锁。
6、newCondition()
返回一个新的condition绑定到该实例Lock示例。
已知有ReentrantLock,ReentrantLockReadWriteLock.ReadLock,ReentrantLockReadWriteLock.WriteLock实现了这个接口。
先看一下ReentrantLock
ReentrantLock和synchronized都是可重入锁,所谓可重入锁就是在一个线程中可以多次获得同一把锁,比如一个线程执行一个带锁的方法,该方法中又调用了另一个需要同样锁的方法,那么该线程可以直接执行调用的方法,而无需重新获得锁。
这种可重入的特性好处就在于避免了死锁的发生,举个知乎上的例子:
public class Parent{
public synchronized void doSomething(){
//to do
}
}
public class Sub extends Parent{
public synchronized doSomething(){
super.doSomething();
}
}
这种情况下如果不是可重入的就会产生死锁,首先理解理解这段代码需要搞懂一个问题。
Sub对象执行doSomething()时候所获取的锁和在这个方法内执行super.doSomething()所需要获取的锁一样吗?
因为执行这两个synchronized方法时的对象都是Sub对象,所以可以看成synchronized(this){//代码段} 因此是一样的。
如果不是可重入的时候,当前线程获得了Sub对象对应的锁,然后由于需要执行super.doSomething(),因此还需要当前的锁。相当于当前线程手握着Sub对象锁去执行一个需要Sub对象锁的方法,如果不是可重入的,就产生了死锁。
然而可重入的特点就使得这样的死锁情况不会发生,执行super.doSomething()的时候不需要重新获取锁,直接执行就可以。
ReentranLock和synchronized很相似,都具有一样的重入特性,但是前者是API层面的互斥锁,后者是原生语法层面的互斥锁。相对于synchronized,ReentranLock增加了一些高级功能:等待可中断,可实现公平锁,锁可以绑定多个条件。
1、等待可中断指的是当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情(tryLock(long time, TimeUnit unit) throws InterruptedException)。
2、公平锁指的是当多个线程在等待同一个锁的时候,必须按照申请锁的时间顺序来依次获得锁,但是非公平锁不一样,锁释放的时候,任何一个等待的线程都有机会获得锁。synchronized中锁是非公平的,ReentrantLock默认情况下是非公平的,但是可以通过带布尔值的构造函数构造公平锁。
3、锁绑定多个条件是指一个ReentrantLock对象可以同时绑定多个Condition对象。
而至于ReentrantReadWriteLock就相当于我们之前说过的Read-Write lock pattern。
jdk1.5及其之前synchronized是悲观锁,lock是乐观锁。并发不严重的时候synchronized比lock好,但是当并发特别严重的时候synchronized性能下降得很快,然而lock能基本保持稳定,这个时候就应该使用lock。
而jdk1.6及其以后synchronized和Lock都是CAS乐观锁,synchronized简单易用,而Lock则能控制并发更精巧,具体选择哪个就看需要了。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。