http://870604904.iteye.com/blog/2258604

第一次写博客,也就是记录一些自己对于JAVA的一些理解,不足之处,请大家指出,一起探讨。

这篇博文我打算说一下JAVA中锁,也就是Lock()的部分源码,这里我拿了一个Lock的具体实现类ReentrantLock来举例,但其实其他几个实现类大同小异。

附上一张流程图,来源我忘记,比较抱歉啊。

首先声明一下ReentrantLock类中的结构

其中有一个Sync静态内部类,该类继承自AbstractQuenedSynchrorizer

在AbstractQuenedSynchrorizer中,实现了大部分关于lock的操作,一般只留下tryAcquire()尝试获取锁,tryRelease()尝试释放锁,延迟到子类来完成,可以提高扩展性

而Sync也有两个子类,分别为NonfairSync与FairSync

那么从lock()入手。

lock()

先调了ReentrantLock中的lock()方法

  1. public void lock() {

  2. sync.lock();
  3. 
}

上面说过了,默认情况下调用lock()方法时调用的是非公平锁,也就是NonfairLock()类中的lock()

  1. final void lock() {

  2. if (compareAndSetState(0, 1))

  3. setExclusiveOwnerThread(Thread.currentThread());

  4. else

  5. acquire(1);

  6. }

这段代码比较好理解,重点放在else部分内,

首先compareAndSetState(0, 1)其实是一个CAS自旋,锁若是未被持有,默认是状态是0,持有后改为1,该方法内部调用的是unsafe的一个自旋,原理就是compareAndSetState(old, new)

若是old值等于期望值,那么将其设置为new值,试想,第一个现成进入if部分,显然可以成功获得锁,并且设置锁的状态为1,那么后面的现成进入后,若是第一个现成不释放锁,之后的现成调用compareAndSetState(0, 1)时,因为old是0,而期望值是1,不相符,所以不会获得该锁。

接下来就是else

  1. public final void acquire(int arg) {

  2. if (!tryAcquire(arg) &&
 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

  3. selfInterrupt();

  4. }

这是AbstractQuenedSynchrorizer中的一个函数,由于尝试获取锁

tryAcquire(arg)显然是调用了NonfairLock类中的tryAcquire()函数,之前也提到了AbstractQuenedSynchrorizer将tryAcquire()尝试获取锁,tryRelease()尝试释放锁,延迟到子类来完成

那么看一看NonfairLock类中tryAcquire()的代码

  1. protected final boolean tryAcquire(int acquires) {
  2. 
    return nonfairTryAcquire(acquires);

  3. }
  4. final boolean nonfairTryAcquire(int acquires) {
  5. 
    final Thread current = Thread.currentThread();

  6. int c = getState();

  7. if (c == 0) {
  8. 
        if (compareAndSetState(0, acquires)) {

  9. setExclusiveOwnerThread(current);

  10. return true;

  11. }

  12. }

  13. else if (current == getExclusiveOwnerThread()) {

  14. int nextc = c + acquires;

  15. if (nextc < 0) // overflow
  16. 
            throw new Error("Maximum lock count exceeded");
  17. 
        setState(next);
  18. 
        return true;
  19. 
    }

  20. return false;

  21. }

getState()获取该锁的状态,初始值为0

也就是说若c==0,则表示该锁未被占用,那么使用compareAndSetState将其设置为1,同时将当前现成置标志位锁的拥有者。

这里其实很好的体现了什么叫非公平锁,试想,当一个现成尝试获取锁时失败看,进入else部分,else内部又让其尝试获取锁,假设之前占有锁的现成在此时释放了锁,那么也就会导致当前线程可以成功的获取到锁,注意,是在第一次获取锁失败之后的一次尝试获取,然后居然就获取成功了,也就是无视了等待队列中的现成,变成了后来者居上的局面。当然也不能说这种非公平方式的获取锁不好,恰恰是这样,大大提高了吞吐量。

那么接下来,若是c!=0呢,进入else部分,判断的条件是当前现成是否是锁的拥有者现成,如果是的话,只是简单的做了个状态+1而已。

若是以上两者情况都不属于,那么返回false,说明该现成当前来看确实无法获取到锁,准备将其插入到等待队列中。

在!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个条件的前半部分已经处理完了,返回若是true,则当前线程获得了锁,否则,没有获得锁

进入后半个判断acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

这里可以看到其内部调了其他一个函数addWaiter(Node.EXCLUSIVE)

  1. private Node addWaiter(Node mode) {
  2. 
    Node node = new Node(Thread.currentThread(), mode);

  3. // Try the fast path of enq; backup to full enq on failure

  4. Node pred = tail;
  5. 
    if (pred != null) {
  6. 
        node.prev = pred;

  7. if (compareAndSetTail(pred, node)) {
  8. 
            pred.next = node;

  9. return node;
  10. 
        }
  11. 
    }

  12. enq(node);

  13. return node;

  14. }

这里需要说明一个问题,就是AbstractQuenedSynchrorizer在内部自己维护了一个双向链表,放的是未获得锁的等待线程

在看这段代码,将当前线程包装成一个Node节点。

获取到该链表的尾节点tail,若尾节点不为null,做一个尾节点与当前新节点的链接,同时compareAndSetTail(pred, node)将tail更新为新加入的节点

若是尾节点为null,调用enq()

  1. private Node enq(final Node node) {
  2. 
    for (;;) {
  3. 
        Node t = tail;
  4. 
        if (t == null) { // Must initialize

  5. if (compareAndSetHead(new Node()))

  6. tail = head;

  7. } else {

  8. node.prev = t;
  9. 
            if (compareAndSetTail(t, node)) {
  10. 
                t.next = node;
  11. 
                return t;

  12. }
  13. 
        }
  14. 
    }
  15. 
}

其大意为若尾节点为null,认为当前链表为空,那么构造一个头结点之后将新节点加入该链表

addWaiter()的核心目的就是将线程包装成节点后加入链表尾部

好了,最后调用if内部执行的函数selfInterrupt();

  1. static void selfInterrupt() {

  2. Thread.currentThread().interrupt();
  3. 
}

中断当前线程,至此一个完整的lock()走完。

接下去就是unlock()

相比之下unlock()比较好理解

  1. public void unlock() {
  2. 
    sync.release(1);
  3. 
}

调用了AbstractQuenedSynchrorizer内的

  1. public final boolean release(int arg) {
  2. 
    if (tryRelease(arg)) {
  3. 
        Node h = head;
  4. 
        if (h != null && h.waitStatus != 0)
  5. 
            unparkSuccessor(h);
  6. 
        return true;

  7. }

  8. return false;

  9. }

类似的,看看tryRelease(arg)做了什么

  1. protected final boolean tryRelease(int releases) {
  2. 
    int c = getState() - releases;
  3. 
    if (Thread.currentThread() != getExclusiveOwnerThread())
  4. 
        throw new IllegalMonitorStateException();

  5. boolean free = false;
  6. 
    if (c == 0) {
  7. 
        free = true;
  8. 
        setExclusiveOwnerThread(null);
  9. 
    }
  10. 
    setState(c);
  11. 
    return free;

  12. }

这里继续说明一下,前面说过AbstractQuenedSynchrorizer将tryAcquire()尝试获取锁,tryRelease()尝试释放锁,延迟到子类来完成,这也是一个体现

这里比较好理解,更新状态,若c==0,setExclusiveOwnerThread(null);设置当前锁未被线程锁拥有,同时设置状态为,若是c不为0,依次释放,知道其为0,然后将该锁的拥有者置为null

返回去看release,获取等待队列的头节点,h != null && h.waitStatus != 0这个条件判断的是头结点是否是一个有效节点,若是调用unparkSuccessor(h);

  1. private void unparkSuccessor(Node node) {
  2. 
    /*
  3. 
     * If status is negative (i.e., possibly needing signal) try

  4. * to clear in anticipation of signalling.  It is OK if this
  5. 
     * fails or if status is changed by waiting thread.

  6. */

  7. int ws = node.waitStatus;
  8. 
    if (ws < 0)
  9. 
        compareAndSetWaitStatus(node, ws, 0);
  10. 

    /*
  11. 
     * Thread to unpark is held in successor, which is normally
  12. 
     * just the next node.  But if cancelled or apparently null,
  13. 
     * traverse backwards from tail to find the actual
  14. 
     * non-cancelled successor.

  15. */

  16. Node s = node.next;
  17. 
    if (s == null || s.waitStatus > 0) {
  18. 
        s = null;
  19. 
        for (Node t = tail; t != null && t != node; t = t.prev)
  20. 
            if (t.waitStatus <= 0)
  21. 
                s = t;
  22. 
    }
  23. 
    if (s != null)

  24. LockSupport.unpark(s.thread);

  25. }

这段代码的意思在于找出第一个可以unpark的线程,一般说来head.next == head,Head就是第一个线程,但Head.next可能被取消或被置为null,因此比较稳妥的办法是从后往前找第一个可用线程。

总结一下,一般来说,在等待队列中的头结点并不是持有锁的节点,而是理解成即将持有锁的节点,因为当锁被释放之后,若是没有被不公平锁的抢占方式抢走了锁,他们头结点是具有获取锁资格的第一人选,若是头结点成功获取到锁,那么他会从链表中脱离,链表更新头结点。

在这里阻塞线程使用的park,同样是unsafe调用了本地方法park()

反之,唤醒线程使用的是unpark(),调用过程同park()

AbstractQuenedSynchrorizer做为一个同步器,是Lock具体实现类的基本功能提供类,像ReentrantLock只是做了该类的一个代理,以及将tryAquire()与tryRelease()的延迟实现。

在每个具体实现部分比如获取锁,释放锁等操作,都调用的CAS自旋操作。

这个我小小的说一下我对于这里为何要使用自旋的原因,首先Lock我们知道是一种轻量级的锁的实现,那么基于这种方式,若是我们想Synchorized方式那样,直接阻塞其余线程,等到有资源的时候再将其唤醒。

一个线程的调度是比较耗费CPU资源的尤其是我们在JVM内部还会实现一些类似于等待队列,运行队列,就绪队列这样的数据结构是,一个线程的切换,不仅仅是将其信息置入到内存,还需要将其在各个队列之间相互转换,就绪队列->运行队列等等。这种情况下,若是我们知道同步操作可以在非常短的时间内完成,那还有比较这样做频繁的线程切换么。

我们大可以将A线程保持其占有处理机的专状态,也就是让其一致在循环运行,循环体可以是空,也可以是一些无意义的指令,等到有资源时直接进入他的工作状态。虽然看起来占着处理机不放不是很好,但是从某种程度上来说,这样会比频繁的切换线程所造成的内存消耗来的更能让人接受。

当然这之间必然有一种平衡,究竟让线程空转多少时间比较合适呢,时间长了明显不合适,短了,又会造成白转的现像。所以这个我个人认为还是主要看运用的场合,若是同步操作很快能完成,那可以用CAS,否则的话,就看如何取舍了。

恩,那这篇差不多写到这里,有不足的地方欢迎大家提出一起研究。

最新文章

  1. java中的SPI机制
  2. 一些gcd计数问题
  3. 【三石jQuery视频教程】01.图片循环展示_再次重发
  4. 我刚知道的WAP app中meta的属性
  5. docker中启动mysql报错
  6. 如何提高nodejs程序的稳定性,健壮性
  7. word添加页码
  8. log4j参数说明
  9. linux下mysql忘记root密码的解决方案
  10. HDU4309-Seikimatsu Occult Tonneru(最大流)
  11. 第一百零七节,JavaScript基本包装类型,数据类型的方法
  12. TextBox只读时不能通过后台赋值取值解决办法
  13. 如何使用git 发布源码到CodePlex
  14. Go并发示例-Pool
  15. 初学mysql 那些记不住的命令
  16. celery 任务队列 + redis
  17. [转自机器之心] 刚入校门的PhD们还可以抢救一下(读研读博指南)
  18. react-router解决锚点跳转问题
  19. NIO学习笔记二
  20. BZOJ1458:士兵占领(有上下界最小流)

热门文章

  1. Linux下报错:Segmentation fault.
  2. Linux下监听或绑定(bind)21端口失败
  3. HDU 1041 Computer Transformation 数学DP题解
  4. VC++动态链接库(DLL)编程深入浅出(四)
  5. 记一个发HTML格式邮件的问题
  6. cart算法
  7. ViewPager+Fragment 滑动菜单效果 实现步骤
  8. VueJS事件处理器v-on:事件修饰符&amp;按键修饰符
  9. Linux系统下授权MySQL账户访问指定数据库和数据库操作
  10. android中使用百度定位sdk实时的计算移动距离