Java并发Concurrent包的锁(一)——Lock接口

Java 并发包 Concurrent 的包结构共可分为五个部分:
- 原子类
- 锁
- collection并发集合框架
- excutor线程池
- 同步工具

本文介绍 Lock 接口和其与 synchronized 关键字的对比。

Lock接口

尽管 synchronized 在语法上已经足够简单了,在 JDK5 之前只能借助此实现,但是由于是独占锁,性能却不高,因此 JDK5 以后就开始借助于 JNI 来完成更高级的锁实现。
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。

void lock()
获取锁。
如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。

void lockInterruptibly()
如果当前线程未被中断,则获取锁。
如果锁可用,则获取锁,并立即返回。
如果锁不可用,出于线程调度目的,将禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态:
锁由当前线程获得;
或者其他某个线程中断当前线程,并且支持对锁获取的中断。

boolean tryLock()
仅在调用时锁为空闲状态才获取该锁。
如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。

boolean tryLock(long time, TimeUnit unit)
如果锁在给定的等待时间内空闲,并且当前线程未被 中断,则获取锁。
如果锁可用,则此方法将立即返回值 true。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在发生以下三种情况之一前,该线程将一直处于休眠状态:

锁由当前线程获得;或者
其他某个线程中断当前线程,并且支持对锁获取的中断;或者
已超过指定的等待时间
如果获得了锁,则返回值 true。

如果当前线程:

在进入此方法时已经设置了该线程的中断状态;或者
在获取锁时被中断,并且支持对锁获取的中断,
则将抛出 InterruptedException,并会清除当前线程的已中断状态。
如果超过了指定的等待时间,则将返回值 false。如果 time 小于等于 0,该方法将完全不等待。

void unlock()
释放锁。
Lock 实现通常对哪个线程可以释放锁施加了限制(通常只有锁的保持者可以释放它),如果违背了这个限制,可能会抛出(未经检查的)异常。该 Lock 实现必须对所有限制和异常类型进行记录。

Condition newCondition()
返回绑定到此 Lock 实例的新 Condition 实例。
在等待条件前,锁必须由当前线程保持。调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。

synchronized和Lock

可以比较一下 synchronized 关键字和 lock 的性能:
(JDK6以后对 synchronized 进行了很多优化,而 Lock 没有太多优化空间,二者其实开销是差不多的。在更新的版本中内置锁只会比显式锁性能更好。)
一个简单的例子:

public class TestLock {

    private static int a = 0;

    private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        final int threadSize = 100000;
        Thread[] ts = new Thread[threadSize];
        for (int i = 0; i < threadSize; i++) {
            ts[i] = new Thread() {
                public void run() {
                    synchronized (obj) {
                        a++;
                    }
                }
            };
        }
        long start = System.currentTimeMillis();
        for(Thread t:ts) {
            t.start();
        }
        for(Thread t:ts) {
            t.join();
        }
        long end = System.currentTimeMillis();
        System.out.println(a);
        System.out.println(end-start);
    }

}

运行结果:

100000
6562

改为使用 Lock 接口实现的 ReentrantLock:

public class TestLock2 {

    private static int a = 0;

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        final int threadSize = 100000;
        Thread[] ts = new Thread[threadSize];
        for (int i = 0; i < threadSize; i++) {
            ts[i] = new Thread() {
                public void run() {
                    lock.lock();
                    try{
                        a++;
                    }finally{
                        lock.unlock();
                    }
                }
            };
        }
        long start = System.currentTimeMillis();
        for(Thread t:ts) {
            t.start();
        }
        for(Thread t:ts) {
            t.join();
        }
        long end = System.currentTimeMillis();
        System.out.println(a);
        System.out.println(end-start);
    }

}

运行结果:

100000
6431

根据多次运行的结果,发现在 JDK8 中两种锁的开销其实是差不多的。
其实是由于这两种锁都是独占锁,JDK5 以前内置锁性能低的原因是它没做任何优化,直接使用系统的互斥体来获取锁。显式锁除了 CAS 的时候利用的是本地代码以外,其它的部分都是 Java 代码实现的,在后续版本的 Java 中,显式锁不太可能会比内置锁好,只会更差。使用显式锁的唯一理由是要利用它更多的功能。

你可能感兴趣的:(Java)