jdk1.7.0_79

  在java.util.concurrent.locks这个包中定义了和synchronized不一样的锁,重入锁——ReentrantLock,读写锁——ReadWriteLock等。在已经有了内置锁synchronized的情况下,为什么又出现了Lock显示锁呢?本文将以Lock作为Java并发包源码解读的开始.

  Lock定义最基本的加锁和解锁操作。

  

Lock

void lock();

阻塞方式获取锁,直到获取锁后才返回

void locklnterruptibly();

获取锁,除非当前线程被中断

Condition newCondition();

返回一个Condition实例绑定到这个锁实例

boolean tryLock();

不管是否获取到锁,都立即返回,非阻塞

boolean tryLock(long time, TimeUnit unit);

在一定时间内阻塞获取锁

void unlock();

释放锁

  Lock接口有一个实现类——重入锁ReentrantLock。进入ReentrantLock类中我们就发现它对于Lock接口的实现基本上都借助于一个抽象静态内部类Sync,该内部类继承自AbstractQueuedSynchronizer,接着又发现两个静态内部类NonfairSync、FairSync,这两个静态内部类又是继承自刚刚的Sync。这里就要引入两个新的概念了——公平锁与非公平锁。在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。(《Java并发编程实战》)

  ReentrantLock你可以称之为重入锁(递归锁)、显示锁、排他锁(独占锁),显示锁很好理解,即线程在获取锁和释放锁的时候都需要代码显示操作。重入锁是什么概念呢?synchronized实际上也是可重入锁,意思就是一个线程已经持有这个锁,当这个线程再次获得这个锁的时候不会被阻塞,而是使其同步状态计数器+1,这样做的目的当然就是为了防止死锁,当线程释放这个锁的时候,同步状态计数器-1,直到递减至0才表示这个锁完全释放完毕,其他线程可以获取。那什么又是排他锁呢?我们直到AQS定义了两种模式下获取锁与释放锁的操作,那就是独占模式和共享模式,所谓独占模式就是只有一个线程能持有这个锁,而共享模式则是这个锁可以由多个线程所持有。例如ReebtrabtReadWriteLock的读锁就能由多个线程所持有。在知道了ReentrantLock的特性之后,我们再来看它其内部实现。

  在前两节解析AQS的时候我们就提到,AQS所提供的同步器是实现锁的基础框架,固然ReentrantLock同样也是基于AQS,而ReentrantLock并没有直接实现AQS抽象类,而是将在其内部定义一个Sync内部类来聚合AQS,这样聚合而不是继承的目的是为了将锁的具体实现与锁的使用做一个隔离,锁的使用者关心的是锁如何才能被正确使用,而锁的实现者关心的是锁如何基于AQS被正确的实现。先讨论ReentrantLock$Sync抽象内部类。在讨论前先回顾一下能够被子类重写的AQS方法有哪些:

  

AbstractQueuedSynchronizer

protected boolean tryAcquire(int arg)

子类可实现在独占模式下获取同步状态的具体方法。

protected boolean tryRelease(int arg)

子类可实现在独占模式下释放同步状态的具体方法。

protected int tryAcquireShared(int arg)

子类可实现在共享模式下获取同步状态的具体方法。

protected int tryReleaseShared()

子类可实现在共享模式下释放同步状态的具体方法。

protected boolean isHeldExclusively()

当前同步器是否在独占模式下被线程占用,一般表示该方法是否被当前线程所独占。

  通过查看ReentrantLock$Sync的源码可知,Sync一共对AQS重写了这么几个方法:

  protected final boolean tryRelease(int release)
  protected final boolean isHeldExclusively()

  为什么Sync只重写了这两个方法呢?实际上在ReentrantLock的内部还有另外两个内部类NonfairSync非公平锁和FairSync公平锁,这两个内部类是Sync的具体实现,很显然能够得出,对于锁的获取非公平锁和公平锁的实现是不一样的,而对于锁的释放两者均是相同实现。针对ReentrantLock的非公平锁和公平锁接下来我们来一一探讨他们的不同点和相同点。

  在ReentrantLock定义了一个成员变量——sync,并且他提供了两个构造方法,其默认无参构造方法创建的是非公平锁,而有参的构造方法则传入一个boolean类型来决定构造一个公平锁还是非公平锁。

public class ReentrantLock implements Lock {
private final Sync sync;
public ReentrantLock() {
sync = new NofairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
……
}

  1.lock()

  针对开篇提到的Lock接口定义的方法,我们先来看ReentrantLock对Lock#lock的实现:

public class ReentrantLock implements Lock {
  ……
  public void lock() {
    sync.lock();
  }  
  ……
}

  这个方法是抽象内部类定义的一个抽象方法,从命名可以看到这个类实际上就是AQS的acquire获取锁的具体实现,在这里我们能看到非公平锁和公平锁对获取锁的不同实现,我们先来看非公平锁对Sync#lock的实现:

static final class NonfairSync extends Sync {
  final void lock() {
    if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
    else
      acquire(1);
  }
  protected final boolean tryAcquire(int acquire) {//在ReentrantLock$NonFairLock才终于看到了对AbstractQueuedSynchronizer#tryAcquire的具体实现。
    return nonfairTryAcquire(acquires);//而tryAcquire的实现实际上又是在其父类ReentrantLock$Lock中实现的,好像有点绕,一会子类实现,一会父类实现。可以先这么来理解,既然它把tryAcquire的具体实现又定义在了父类,那说明这一定是父类对公共方法的抽取(Extract Method),其他地方一定有用到nonfairTryAcquire方法,不然JDK的作者不会闲的蛋疼。
  }
}

  ReentrantLock$Sync中非公平锁的获取锁的实现

final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

  我们说回ReentrantLock$NonFairLock非公平锁的lock阻塞获取锁的实现,在调用非公平锁的lock方法时,就会首先进行“抢锁”操作,也就是compareAndSetState这个方法是利用的CAS底层方法看能否抢占到锁,而不是按照先后获取获取锁的方式放到同步队列中获取锁,公平锁就是这样。既然说到了公平锁获取锁的方式,我们不妨和ReentrantLock$FairLock作一个对比:

static final class FairSync extends Sync {
  inal void lock() {
    acquire(1);   }
  protected final boolean tryAcquire(int acquires) {//在ReentrantLock$NonFairLock我们看到它老老实实的实现了AQS定义的tryAcquire方法,而没有调用父类的方法,从这里我们也基本能推断在ReentrantLock中没有其他地方会引用到这个方法。
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
      if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
      }
    }
    else if (current == getExclusiveOwnerThread()) {
      int nextc = c + acquires;
      if (nextc < 0)
        throw new Error(“Maximum lock count exceeded”);
      setState(nextc);
      return true;
    }    
    return false;
  }
}

  从公平锁(nonfairTryAcquire)和非公平锁(tryAcquire)的两个方法对比可知:公平锁在获取锁的时候首先会判断当前线程是否有前驱节点已经在队列中等待,如果有返回true,则不进行获取锁的操作,这一点就是和公平锁最大的区别,只有当前线程没有前驱节点才获取锁。ReentrantLock默认构造非公平锁,而实际上用得最多的也是非公平锁,公平锁从一定程度上能防止“饥饿”,但非公平锁在性能上却优于公平锁,我们做以下试验得知的确如此(详细试验过程《【试验局】ReentrantLock中非公平锁与公平锁的性能测试》

  2.lockInterruptibly()

这个方法和lock方法的区别就是,lock会一直阻塞下去直到获取到锁,而lockInterruptibly则不一样,它可以响应中断而停止阻塞返回。ReentrantLock对其的实现是调用的Sync的父类AbstractQueuedSynchronizer#acquireInterruptibly方法:

//ReentrantLock#lockInterruptibly
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);//因为ReentrantLock是排它锁,故调用AQS的acquireInterruptibly方法
}
//AbstractQueuedSynchronizer#acquireInterruptibly 

public final void acquireInterruptibly(int arg) throws InterruptedException{
  if (Thread.interrupted()) //线程是否被中断,中断则抛出中断异常,并停止阻塞
    throw new InterruptedException;
  if (!tryAcquire(arg)) //首先还是获取锁,具体参照上文
    doAcquireInterruptibly(arg);//独占模式下中断获取同步状态

  通过查看doAcquireInterruptibly的方法实现不难发现它和acquireQueued大同小异,前者抛出异常,后者返回boolean。具体实在不再讨论,参照源码以及《2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放》

  3.tryLock()

  此方法为非阻塞式的获取锁,不管有没有获取锁都返回一个boolean值。

//ReentrantLock#tryLock
public boolean tryLock() {
  return sync.nonfairTryAcquire(1);
}

  可以看到它实际调用了Sync#nonfairTryAcquire非公平锁获取锁的方法,这个方法我们在上文lock()方法非公平锁获取锁的时候有提到,而且还特地强调了该方法不是在NonfairSync实现,而是在Sync中实现很有可能这个方法是一个公共方法,果然在非阻塞获取锁的时候调用的是此方法。详细解析参照上文。

  4.tryLock(long timeout, TimeUnit unit)

此方法是表示在超时时间内获取到同步状态则返回true,获取不到则返回false。由此可以联想到AQS的tryAcquireNanos(int arg, long nanosTimeOut)方法

//ReentrantLock#tryLock
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
  return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

  果然Sync实际上调用了父类AQS的tryAcquireNanos方法。

//AbstractQueuedSynchronizer#tryAcquireNanos 

public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
  if (Thread.interrupted())
    throw new InterruptedException();//可以看到前面和lockInterruptibly一样
  return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);//首先也会先尝试获取锁
}

  在doAcquireNanos实际也和acquireQueued、doAcquireInterruptibly差不多,不同的是增加了超时判断。

  关于Lock和ReentrantLock介绍到这里,在AQS和这里遗留了一个问题——Condition,在下一节中单独介绍Condition。

5.Lock接口及其实现ReentrantLock的更多相关文章

  1. jdk1.5多线程Lock接口及Condition接口

    jdk1.5多线程的实现的方式: jdk1.5之前对锁的操作是隐式的 synchronized(对象) //获取锁 { } //释放锁 jdk1.5锁的操作是显示的:在包java.util.concu ...

  2. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  3. Java多线程的~~~Lock接口和ReentrantLock使用

    在多线程开发.除了synchronized这个keyword外,我们还通过Lock接口来实现这样的效果.由Lock接口来实现 这样的多线程加锁效果的优点是非常的灵活,我们不在须要对整个函数加锁,并且能 ...

  4. 线程同步 Lock接口

    同步:★★★★★ 好处:解决了线程安全问题. 弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁. 定义同步是有前提的: 1,必须要有两个或者两个以上的线程,才需要同步. 2,多个线程必须保证使用 ...

  5. Java基础知识强化之多线程笔记06:Lock接口 (区别于Synchronized块)

    1. 简介 我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式 ...

  6. Java多线程(五) Lock接口,ReentranctLock,ReentrantReadWriteLock

    在JDK5里面,提供了一个Lock接口.该接口通过底层框架的形式为设计更面向对象.可更加细粒度控制线程代码.更灵活控制线程通信提供了基础.实现Lock接口且使用得比较多的是可重入锁(Reentrant ...

  7. 多线程里面的关键字,wait, notfiy, 锁(synchronized), lock接口

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  8. java多线程Lock接口简介使用与synchronized对比 多线程下篇(三)

    前面的介绍中,对于显式锁的概念进行了简单介绍 显式锁的概念,是基于JDK层面的实现,是接口,通过这个接口可以实现同步访问 而不同于synchronized关键字,他是Java的内置特性,是基于JVM的 ...

  9. java 锁 Lock接口详解

    一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...

随机推荐

  1. mysql中 decimal、numeric数据类型

    例 如:salary DECIMAL(5,2) 在这个例子中,5 (精度(precision)) 代表重要的十进制数字的数目,2 (数据范围(scale)) 代表在小数点后的数字位数.在这种情况下,因 ...

  2. JVM年轻代、年老代、永久代

    年轻代: HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫From和To),每次新创建对象时,都会分配到Eden区,当Eden区没有足够的空间进行分配时,虚拟 ...

  3. c++设计成员变量可动态调整的动态类结构

    本文主要介绍一下如何使用c++设计成员变量可动态调整的抽象动态类结构.首先介绍一下项目中以前使用的一种类结构:静态类结构 1.静态类结构 很多时候,在项目开发中设计类结构时,我们往往有一种简单.直接的 ...

  4. 使用JS实现鼠标悬浮切换显示

    实现的是在鼠标悬停在不同链接上,在同一位置切换显示想要显示的内容 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional// ...

  5. AM335X开发板学习系列——环境搭建(vbox虚拟机ubuntu14.04下minicom的安装和配置)

    这个系列是我学习AM335X的总结. 1. ubuntu虚拟机的USB设备,选择启用usbserial 2. ubuntu虚拟机的网络,采用桥接模式,以保证开发板和ubuntu虚拟机能互相ping通 ...

  6. openMP编程(上篇)之指令和锁

    openMP简介 openMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的. 当计算机升级到多核时,程序中创建的线程数量需要随CPU核数变化,如在CPU核数超过线程数 ...

  7. html 压缩工具 html-minifier

    https://github.com/kangax/html-minifier#options-quick-reference 1.参数列表 option Description Default re ...

  8. iOS多线程的三种方法

    前言 在多线程简介中,我已经说明过了,为了提高界面的流畅度以及用户体验.我们务必要把耗时的操作放到别的线程中去执行,千万不要阻塞主线程.iOS中有以下3种多线程编程方法: NSThread Grand ...

  9. Set ,List,ArrayList,LinkedList,Vectory,HashMap,Hashtable,HashSet,TreeSet,TreeSet

    Set与List区别: 两者都是接口,并继承Collection接口:List有序,允许重复:Set无序,不能重复: ArrayList与LinkList区别: ArrayList是动态数组,查询效率 ...

  10. 分布式键值存储系统ETCD调研

    分布式键值存储系统ETCD调研 简介 etcd是一个开源的分布式键值存储工具--为CoreOS集群提供配置服务.发现服务和协同调度.Etcd运行在集群的每个coreos节点上,可以保证coreos集群 ...