多线程编程中,当代码须要同步时咱们会用到锁。Java为咱们提供了内置锁(synchronized
)和显式锁(ReentrantLock
)两种同步方式。显式锁是JDK1.5引入的,这两种锁有什么异同呢?是仅仅增长了一种选择仍是另有其因?本文为您一探究竟。html
Java内置锁经过synchronized关键字使用,使用其修饰方法或者代码块,就能保证方法或者代码块以同步方式执行。使用起来很是近简单,就像下面这样:java
// synchronized关键字用法示例 public synchronized void add(int t){// 同步方法 this.v += t; } public static synchronized void sub(int t){// 同步静态方法 value -= t; } public int decrementAndGet(){ synchronized(obj){// 同步代码块 return --v; } }
这就是内置锁的所有用法,你已经学会了。git
内置锁使用起来很是方便,不须要显式的获取和释放,任何一个对象都能做为一把内置锁。使用内置锁可以解决大部分的同步场景。“任何一个对象都能做为一把内置锁”也意味着出现synchronized关键字的地方,都有一个对象与之关联,具体说来:github
内置锁这么好用,为何还需多出一个显式锁呢?由于有些事情内置锁是作不了的,好比:编程
显式锁(ReentrantLock)正式为了解决这些灵活需求而生。ReentrantLock的字面意思是可重入锁,可重入的意思是线程能够同时屡次请求同一把锁,而不会本身致使本身死锁。下面是内置锁和显式锁的区别:api
可定时:RenentrantLock.tryLock(long timeout, TimeUnit unit)
提供了一种以定时结束等待的方式,若是线程在指定的时间内没有得到锁,该方法就会返回false并结束线程等待。缓存
可中断:你必定见过InterruptedException,不少跟多线程相关的方法会抛出该异常,这个异常并非一个缺陷致使的负担,而是一种必须,或者说是一件好事。可中断性给咱们提供了一种让线程提早结束的方式(而不是非得等到线程执行结束),这对于要取消耗时的任务很是有用。对于内置锁,线程拿不到内置锁就会一直等待,除了获取锁没有其余办法可以让其结束等待。RenentrantLock.lockInterruptibly()
给咱们提供了一种以中断结束等待的方式。安全
条件队列(condition queue):线程在获取锁以后,可能会因为等待某个条件发生而进入等待状态(内置锁经过Object.wait()
方法,显式锁经过Condition.await()
方法),进入等待状态的线程会挂起并自动释放锁,这些线程会被放入到条件队列当中。synchronized对应的只有一个条件队列,而ReentrantLock能够有多个条件队列,多个队列有什么好处呢?请往下看。markdown
条件谓词:线程在获取锁以后,有时候还须要等待某个条件知足才能作事情,好比生产者须要等到“缓存不满”才能往队列里放入消息,而消费者须要等到“缓存非空”才能从队列里取出消息。这些条件被称做条件谓词,线程须要先获取锁,而后判断条件谓词是否知足,若是不知足就不往下执行,相应的线程就会放弃执行权并自动释放锁。使用同一把锁的不一样的线程可能有不一样的条件谓词,若是只有一个条件队列,当某个条件谓词知足时就没法判断该唤醒条件队列里的哪个线程;可是若是每一个条件谓词都有一个单独的条件队列,当某个条件知足时咱们就知道应该唤醒对应队列上的线程(内置锁经过Object.notify()
或者Object.notifyAll()
方法唤醒,显式锁经过Condition.signal()
或者Condition.signalAll()
方法唤醒)。这就是多个条件队列的好处。多线程
使用内置锁时,对象自己既是一把锁又是一个条件队列;使用显式锁时,RenentrantLock的对象是锁,条件队列经过RenentrantLock.newCondition()
方法获取,屡次调用该方法能够获得多个条件队列。
一个使用显式锁的典型示例以下:
// 显式锁的使用示例 ReentrantLock lock = new ReentrantLock(); // 获取锁,这是跟synchronized关键字对应的用法。 lock.lock(); try{ // your code }finally{ lock.unlock(); } // 可定时,超过指定时间为获得锁就放弃 try { lock.tryLock(10, TimeUnit.SECONDS); try { // your code }finally { lock.unlock(); } } catch (InterruptedException e1) { // exception handling } // 可中断,等待获取锁的过程当中线程线程可被中断 try { lock.lockInterruptibly(); try { // your code }finally { lock.unlock(); } } catch (InterruptedException e) { // exception handling } // 多个等待队列,具体参考[ArrayBlockingQueue](https://github.com/CarpenterLee/JCRecipes/blob/master/markdown/ArrayBlockingQueue.md) /** Condition for waiting takes */ private final Condition notEmpty = lock.newCondition(); /** Condition for waiting puts */ private final Condition notFull = lock.newCondition();
注意,上述代码将unlock()
放在finally块里,这么作是必需的。显式锁不像内置锁那样会自动释放,使用显式锁必定要在finally块中手动释放,若是获取锁后因为异常的缘由没有释放锁,那么这把锁将永远得不到释放!将unlock()放在finally块中,保证不管发生什么都可以正常释放。
内置锁可以解决大部分须要同步的场景,只有在须要额外灵活性是才须要考虑显式锁,好比可定时、可中断、多等待队列等特性。
显式锁虽然灵活,可是须要显式的申请和释放,而且释放必定要放到finally块中,不然可能会由于异常致使锁永远没法释放!这是显式锁最明显的缺点。
综上,当须要同步时请优先考虑更安全的更易用的隐式锁。
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html