Java Lock示例 - ReentrantLock

引言

在多线程环境下,一般咱们使用 synchronized 关键字来保证线程安全。java

大多数状况下,用 synchronized 关键字就足够了,但它也有一些缺点, 因此在 Java Concurrency 包中引入了 Lock API 。从Java 1.5版开始在 java.util.concurrent.locks 包中提供了处理并发的 Concurrency API 的 Lock 锁接口和一些实现类来改进 Object 锁定机制。安全

Java Lock API中的一些重要接口和类

Java Lock API中的一些重要接口和类是:多线程

  • 锁(Lock):这是Lock API的基本接口。它提供了 synchronized 关键字的全部功能,以及为锁定建立不一样条件的其余方法,为线程等待锁定提供超时功能。一些重要的方法是 lock() 获取锁,unlock() 释放锁,tryLock() 等待锁定一段时间,newCondition() 建立条件等。
  • 条件(Condition):条件对象相似于对象等待通知( Object wait-notify)模型,具备建立不一样等待集的附加功能。Condition 对象始终由 Lock 对象建立。一些重要的方法是 await(),相似于Object.wait() 和 signal(),signalAll(),相似于 Object.notify() 和 Object.notifyAll() 方法。
  • 读写锁(ReadWriteLock):它包含一对关联的锁,一个用于只读操做,另外一个用于写入。只要没有写入线程,读锁能够由多个读取线程同时保持。写锁是独占的。
  • 重入锁(ReentrantLock):这是最普遍使用的 Lock 接口实现类。此类以与 synchronized 关键字相似的方式实现 Lock 接口。除了 Lock 接口实现以外,ReentrantLock 还包含一些实用程序方法来获取持有锁的线程,等待获取锁线程等。

synchronized 块

synchronized 块本质上是可重入的,即若是一个线程锁定了监视对象,而且另外一个同步块须要锁定在同一个监视对象上,则线程能够进入该代码块。我认为这就是类名是ReentrantLock的缘由。让咱们经过一个简单的例子来理解这个特性。并发

public class Test{

public synchronized foo(){
    //do something
    bar();
  }

  public synchronized bar(){
    //do some more
  }
}

若是一个线程进入 foo(),它就会锁定Test对象,因此当它尝试执行 bar() 方法时,容许该线程执行 bar() 方法,由于它已经在 Test 对象上持有锁,即与 synchronized(this) 效果是同样的。ide

Java Lock 示例 - Java 中的 ReentrantLock

如今让咱们看一个简单的例子,咱们将使用 Java Lock API 替换 synchronized 关键字。post

假设咱们有一个 Resource 类,其中包含一些操做,咱们但愿它是线程安全的,以及一些不须要线程安全的方法。this

package com.journaldev.threads.lock;

public class Resource {

    public void doSomething(){
        //do some operation, DB read, write etc
    }
    
    public void doLogging(){
        //logging, no need for thread safety
    }
}

如今假设咱们有一个 Runnable 类,咱们将使用 Resource 方法。google

package com.journaldev.threads.lock;

public class SynchronizedLockExample implements Runnable{

    private Resource resource;
    
    public SynchronizedLockExample(Resource r){
        this.resource = r;
    }
    
    @Override
    public void run() {
        synchronized (resource) {
            resource.doSomething();
        }
        resource.doLogging();
    }
}

请注意,我使用 synchronized 块来获取 Resource 对象上的锁。咱们能够在类中建立一个虚拟对象,并将其用于锁定的目的。spa

如今让咱们看看咱们如何使用 Java Lock API 并重写上面的程序而不使用 synchronized 关键字。咱们将在Java 中使用 ReentrantLock。线程

package com.journaldev.threads.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrencyLockExample implements Runnable{

    private Resource resource;
    private Lock lock;
    
    public ConcurrencyLockExample(Resource r){
        this.resource = r;
        this.lock = new ReentrantLock();
    }
    
    @Override
    public void run() {
        try {
            if(lock.tryLock(10, TimeUnit.SECONDS)){
               resource.doSomething();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //release lock
            lock.unlock();
        }
        resource.doLogging();
    }

}

正如你所看到的,我正在使用 tryLock() 方法来确保个人线程只等待必定的时间,若是它没有得到对象的锁定,它只是记录和退出。另外一个要注意的重点是使用 try-finally 块来确保即便 doSomething() 方法调用抛出任何异常也会释放锁定。

Java Lock 与 synchronized 比较

基于以上细节和程序,咱们能够很容易地得出 Java Lock 和同步之间的如下差别。

  • Java Lock API 为锁定提供了更多的可见性和选项,不像在线程可能最终无限期地等待锁定的同步,咱们可使用tryLock() 来确保线程仅等待特定时间。
  • 同步关键字的代码更清晰,更易于维护,而使用 Lock,咱们不得不尝试使用 try-finally 块来确保即便在 lock() 和 unlock() 方法调用之间抛出异常也会释放 Lock。
  • 同步块或方法只能覆盖一种方法,而咱们能够在一种方法中获取锁,并使用 Lock API 在另外一种方法中释放它。
  • synchronized 关键字不提供公平性,而咱们能够在建立 ReentrantLock 对象时将公平性设置为 true,以便最长等待的线程首先得到锁定。
  • 咱们能够为 Lock 建立不一样的等待条件(Condition),不一样的线程能够针对不一样的条件来 await() 。

这就是 Java Lock 示例,Java 中的 ReentrantLock 以及使用 synchronized 关键字的比较分析。

做 者:

关于Pankaj
8087ef00af2610b870ada12392286695?s=120&d=blank&r=g
若是你走得这么远,那就意味着你喜欢你正在读的东西。为何不直接在Google PlusFacebookTwitter上与我联系。我很想直接听到你对个人文章的想法和意见。

最近我开始建立视频教程,因此请在Youtube上查看个人视频。

相关文章
相关标签/搜索