显示锁Lock是在Java5中添加到jdk的,同synchronized一样,这也是一种协调共享对象访问的机制。但是它不是用来替代内置锁的,而是一种可选择的高级功能。
显示锁Lock提供了一种无条件的,可轮询的,定时的,可中断的锁机制,所有的加锁和解锁都是显示的。ReentrantLock实现了Lock接口,并有着和synchronized相同的互斥性和内存可见性。
我们来看Lock接口
public interface Lock {
public abstract void lock();
public abstract void lockInterruptibly() throws InterruptedException;
public abstract boolean tryLock();
public abstract boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
public abstract void unlock();
public abstract Condition newCondition();
}
无条件,可中断锁,轮询锁,定时锁分别对应了Lock接口中的前四个方法。
显示锁的无条件的锁获获取方式跟内置锁synchronized一样,只不过它的释放必须跟显示的,不然锁会一直无法释放。这是显示锁无法替代内置锁的一个原因。
public class ReentrantLockDemo {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
es.execute(new Work());
}
}
}
class Work implements Runnable {
private static Lock lock = new ReentrantLock();
private static int count = 0;
private int id;
public Work() {
this.id = count++;
}
@Override
public void run() {
//无条件获得锁,保持
lock.lock();
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(id);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
lock.unlock();
}
}
}
显示锁的可中断的锁获取方式可以在等待锁的同时保持对中断的响应。也就是说,只有当该线程可中断的情况下才能获取锁。
public class InterruptiblyLockDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
WorkManager manager = new WorkManager(lock);
Work work = new Work(lock);
Thread t1 = new Thread(manager);
Thread t2 = new Thread(work);
manager.setWork(t2);
t1.start();
try {
// sleep1秒,保证WorkManager线程启动成功
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class Work implements Runnable {
private Lock lock;
public Work(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
System.out.println(0);
try {
lock.lockInterruptibly();
try {
System.out.println("do some work");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("Interrupt");
}
}
}
class WorkManager implements Runnable {
private Lock lock;
private Thread workThread;
public WorkManager(Lock lock) {
this.lock = lock;
}
public void setWork(Thread workThread) {
this.workThread = workThread;
}
@Override
public void run() {
lock.lock();
try {
// 持有锁,并sleep2秒,以便work线程成功启动
TimeUnit.SECONDS.sleep(2);
// 中断work线程
if (workThread != null) {
workThread.interrupt();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
如果work线程使用lock.lock的无条件锁获取方式,那么这个例子中,是不会响应中断的。ps,定时的锁获取方式也是可以响应中断的。
注意:可中断的锁获取方式会抛出异常,需要有两个try块,一个是用于中断,另一个用于将解锁unlock放于finally块中。如果合并成同一个try块,那么当线程在获取锁的时候发生中断,系统会出现java.lang.IllegalMonitorStateException,这是因为线程被中断,线程不是lock锁的持有者,却执行了释放锁的操作。这个跟notify,notifyAll在非同步块中调用抛出的IllegalMonitorStateException是同样的,即在没有该锁的情况下去执行了解锁操作。可以看下java.lang.IllegalMonitorStateException
可轮询的和定时的锁获取方式都是由tryLock方法实现的。这两种方式可以避免死锁的发生。如果不能获得所有需要的锁,那么线程是释放已经获得的锁,然后重新尝试获取所有锁。
究竟采用内置锁还是显示锁?性能上,java6以后,两者性能比较接近。不过程序运行平台差别大,生产环境可能在必要的时候进行尝试才能确定性能的优缺。显示锁的可轮询,可定时,可中断也是我们选择显示锁的原因。但是内置锁也有比较大的优势。开发人员熟悉内置锁,内置锁简洁紧凑,程序中已经大量使用内置锁,最主要的是,显示锁的危险性比较高,开发人员绝对不能忘记在finally中调用unlock。所以一般还是使用内置锁,只有在内置锁无法满足需求,或者调优的情况下才使用显示锁。