[Java源码][并发J.U.C]---用代码一步步实现AQS(2)---独占锁中断式获取

前言

本文接着上文 [Java源码][并发J.U.C]---用代码一步步实现AQS(1)---独占锁 继续分析AQS中的其他源码, 包括完善mutex类中的其他方法.

本文源代码: 源码

上文分析到了AQS中的acquire方法获得锁是不响应线程的, 接下来分析如何响应中断式的获取锁.

响应中断式的获取锁

Mutex类中修改lockInterruptibly方法如下:

@Override
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

AQS中加入如下代码:

    private void doAcquireInterruptibly(int arg)
            throws InterruptedException {
            final Node node = addWaiter(Node.EXCLUSIVE);
            boolean failed = true;
            try {
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                  /**
                   * 当判断是被中断而不是被唤醒的时候,抛出InterruptedException
                   * 
                   */
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())  // 2
                        throw new InterruptedException();  
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        /**
         * 如果当前线程已经被中断了 直接抛出InterruptedException
         * 注意:Thread.interrupted()会在复位当前线程的中断状态 也就是变为false
         */
        if (Thread.interrupted())       // 1
            throw new InterruptedException();
        // 尝试获取锁 如果获取不到则加入到阻塞队列中
        if (!tryAcquire(arg))  
            doAcquireInterruptibly(arg);
    }

这里与acquire(int arg)有两点区别(分别在代码中的1和2处的代码):
1. 如果当前线程已经被中断了, 会抛出InterruptedException,并且中断状态会被复位成false,因为使用的是Thread.interrupted().
2. 在确定是被中断的时候,会抛出InterruptedException,这里需要注意两点.

注意:
1. parkAndCheckInterrupt()中使用的是Thread.interrupted()方法,因此该方法会把中断状态复位成false,因此整个acquireInterruptibly(int arg)方法如果抛出InterruptedException异常的话中断状态也会被复位成false.
2. 此时抛出异常, failed依然为true, 会执行cancelAcquire(node)方法取消当前线程所对应的节点,也就是从等待队列中去除. 然后从doAcquireInterruptibly(int arg)方法中退出.

从如下的流程图中可以更清楚的看看基本逻辑.


juc_5(3).png

接下来看个简单的例子测试一下

例子1 : 测试中断式获取锁

生成两个线程分别为thread-1thread-2, 让thread-1获得锁,并让thread-2加入该锁的等待队列中, 在thread-1还没有释放锁前也就是thread-2没有获得锁前中断thread-2看看会发生什么.

import java.util.concurrent.TimeUnit;
import com.sourcecode.locks.Test.Runner;

public class TestLockInterruptedException {
    public static void main(String[] args) {
        Mutex m = new Mutex();
        Thread thread_1 = new Thread(new Runner(m), "thread-1");
        Thread thread_2 = new Thread(new Runner(m), "thread-2");
        thread_1.start();
        try {
            TimeUnit.SECONDS.sleep(1); //让thread-1获得锁
            thread_2.start();
            TimeUnit.SECONDS.sleep(1); //让thread-2充分进入到等待队列中
            m.printWaitingNode();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread_2.interrupt();
    }
    
    static class Runner implements Runnable {
        Mutex m;
        public Runner(Mutex m) {
            this.m = m;
        }
        @Override
        public void run() {
            boolean getLock = true;
            try {
                m.lockInterruptibly();
            } catch (Exception e) {
                e.printStackTrace();
                //Thread.currentThread().interrupt(); //报告一下中断状态  因为抛出异常前中断状态被清空了
                getLock = false;
            }
            System.out.println(Thread.currentThread().getName() + " runs, getLock: " + getLock);
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(getLock) m.unlock();
        }
    }
}

测试结果如下: thread-2会进入到catch语句块中并且它的中断状态已经被复位了.

thread-1 runs, getLock: true
[NULL,-1]->[thread-2,0]->
java.lang.InterruptedException
thread-2 intrrupted status:false
thread-2 runs, getLock: false
    at com.sourcecode.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:357)
    at com.sourcecode.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:375)
    at com.sourcecode.locks.Mutex.lockInterruptibly(Mutex.java:41)
    at com.sourcecode.locks.TestLockInterruptedException$Runner.run(TestLockInterruptedException.java:32)
    at java.lang.Thread.run(Thread.java:745)

但是如果把catch语句块中的注释打开会发生什么呢?

thread-1 runs, getLock: true
[NULL,-1]->[thread-2,0]->
java.lang.InterruptedException
thread-2 intrrupted status:false
thread-2 runs, getLock: false
    at com.sourcecode.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:357)
    at com.sourcecode.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:375)
    at com.sourcecode.locks.Mutex.lockInterruptibly(Mutex.java:41)
    at com.sourcecode.locks.TestLockInterruptedException$Runner.run(TestLockInterruptedException.java:32)
    at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.sourcecode.locks.TestLockInterruptedException$Runner.run(TestLockInterruptedException.java:41)
    at java.lang.Thread.run(Thread.java:745)

可以从结果中看到TimeUnit.SECONDS.sleep(10);也抛出了异常,原因不难找到, 从下面sleep的源码中可以看到如果当前线程的中断状态是true的时候, 该方法会认为该线程被中断了,异常会抛出异常并且复位它的中断异常状态. 关于异常可以看我的另外一篇博客 [并发J.U.C] 用例子理解线程中断

 * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

接下来看看tryLock方法.

tryLock方法 尝试性的去获取锁

那什么叫尝试性的去获取锁?在接口Lock中有定义

// 获取锁 如果锁是available立即返回true, 如果锁不存在就立即返回false
    boolean tryLock();

接下来看看是如何实现的, 先在Mutex类中修改

    @Override
    public boolean tryLock() {
        // TODO Auto-generated method stub
        return sync.tryAcquire(1);
    }

可以看到很简单,直接调用了sync自己实现的tryAcquire, 如果锁是可以得到的,则立即返回true表明已经获得了锁, 否则立马返回, 不会进入到锁的等待队列中.

简单看一个tryLock的小例子

例子2: tryLock

import java.util.concurrent.TimeUnit;
public class TestTryLock {
    public static void main(String[] args) {
        Mutex m = new Mutex();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runner(m), "thread-" + i).start();;
        }
        try {
            TimeUnit.SECONDS.sleep(3); // 为了让每个thread充分运行
            m.printWaitingNode();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    static class Runner implements Runnable {
        Mutex m;
        public Runner(Mutex m) {
            this.m = m;
        }
        @Override
        public void run() {
            if (m.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " get lock and runs");
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                m.unlock();
            } else {
                System.out.println(Thread.currentThread().getName() + " does not get lock and runs");
            }
        }
    }
}

输出如下: 都没有进入到等待队列中.

thread-1 get lock and runs
thread-3 does not get lock and runs
thread-0 does not get lock and runs
thread-2 does not get lock and runs
thread-4 does not get lock and runs

接下来看看 tryLock的另外一种形式tryLock(long time, TimeUnit unit) throws InterruptedException

等待式并且响应中断式的tryLock->tryLock(long time, TimeUnit unit) throws InterruptedException

先直接看源码吧, 在Mutex类中加入如下代码:

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // TODO Auto-generated method stub
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

AQS 中加入

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

这次直接对比响应中断的获取锁的doAcquireInterruptibly方法的主要区别是下面这段代码:

nanosTimeout = deadline - System.nanoTime();                    // 1
if (nanosTimeout <= 0L)                                         // 2
        return false;
if (shouldParkAfterFailedAcquire(p, node) &&
        nanosTimeout > spinForTimeoutThreshold)                 // 3
        LockSupport.parkNanos(this, nanosTimeout);  
if (Thread.interrupted())                                       // 4
       throw new InterruptedException();

1. 计算当前剩下多长时间
2. 判断是否有超过所传入的等待时间
3. 判断是否需要进行休眠
4. 如果该线程被中断, 抛出异常

其实与doAcquireInterruptibly方法类似, 只是加了个超时返回的操作.

例子3: tryLock(long time, TimeUnit unit) throws InterruptedException

启动5个线程去超时获得锁.

import java.util.concurrent.TimeUnit;

public class TestTryLockTime {
    public static void main(String[] args) {
        Mutex m = new Mutex();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runner(m), "thread-" + i).start();;
        }
        try {
            TimeUnit.SECONDS.sleep(3); // 为了让每个thread充分运行
            m.printWaitingNode();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    static class Runner implements Runnable {
        Mutex m;
        public Runner(Mutex m) {
            this.m = m;
        }
        @Override
        public void run() {
            boolean getLock = false;
            try {
                getLock = m.tryLock(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            if (getLock && !Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + " get lock and runs");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                m.unlock();
            } else {
                System.out.println(Thread.currentThread().getName() + " does not get lock and runs");
            }
        }
    }
}

输出如下: thread-1thread-3获得了锁,而其他线程由于超时等待返回了

thread-1 get lock and runs
[NULL,-1]->[thread-3,-1]->[thread-0,-1]->[thread-2,-1]->[thread-4,0]->
thread-3 get lock and runs
thread-4 does not get lock and runs
thread-0 does not get lock and runs
thread-2 does not get lock and runs

关于异常部分与例子1类似,便不再写例子了.

参考

1. Java并发编程的艺术
2. Java1.8 java.util.concurrent.locks包的源代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容