前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为什么Synchronized不可中断?

为什么Synchronized不可中断?

作者头像
蒋老湿
发布2020-04-30 15:53:08
4.4K0
发布2020-04-30 15:53:08
举报
文章被收录于专栏:技术栈技术栈技术栈

为什么Synchronized不可中断?首先中断操作是Thread类调用interrupt方法实现的。基本上所有人都说Synchronized后线程不可中断,百度后的大部分文章都是这样解释说道:

不可中断的意思是等待获取锁的时候不可中断,拿到锁之后可中断,没获取到锁的情况下,中断操作一直不会生效。

验证真伪

以下为测试理论是否成立的Demo代码示例:

public class Uninterruptible {
    private static final Object o1 = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            System.out.println("t1 enter");
            synchronized (o1) {
                try {
                    System.out.println("start lock t1");
                    Thread.sleep(20000);
                    System.out.println("end lock t1");
                } catch (InterruptedException e) {
                    System.out.println("t1 interruptedException");
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("t2 enter");
            synchronized (o1) {
                try {
                    System.out.println("start lock t2");
                    Thread.sleep(1000);
                    System.out.println("end lock t2");
                } catch (InterruptedException e) {
                    System.out.println("t2 interruptedException");
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

        // 主线程休眠一下,让t1,t2线程百分百已经启动,避免线程交替导致测试结果混淆
        Thread.sleep(1000);
        // 中断t2线程的执行
        thread2.interrupt();
        System.out.println("t2 interrupt...");

    }
}
复制代码

运行结果:

t1 enter
start lock t1
t2 enter
t2 interrupt...   // 此处等待了好久好久,一直卡住

end lock t1       
start lock t2     // 直到t1执行完释放锁后,t2拿到锁准备执行时,interruptedException异常抛出
t2 interruptedException
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at concurrent.Uninterruptible.lambda$main$1(Uninterruptible.java:48)
    at concurrent.Uninterruptible$$Lambda$2/1134517053.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

Process finished with exit code 0

结果正好印证了Synchronized不可中断的说法:只有获取到锁之后才能中断,等待锁时不可中断。

深入分析Synchronized

为什么Synchronized要设计成这样,ReentrantLock都允许马上中断呀,是Synchronized设计者有意为之还是另有苦衷? 感觉如果设计成这样有点蠢吧,为什么要拿到锁才去中断,毫无理由啊。肯定有阴谋!

后来看了Thread.interrupt()源码发现,这里面的操作只是做了修改一个中断状态值为true,并没有显式声明抛出InterruptedException异常。

/**
 * <p> If this thread is blocked in an invocation of the {@link
 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
 * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
 * class, or of the {@link #join()}, {@link #join(long)}, {@link
 * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
 * methods of this class, then its interrupt status will be cleared and 
 * it will receive an {@link InterruptedException}.
 * 翻译:如果此线程被以下命令(wait、join、sleep)阻塞,他的中断状态会被
 * 清除并且会抛出InterruptedException异常
 *
 * <p> If none of the previous conditions hold then this thread's 
 * interrupt status will be set. </p>
 * 翻译:如果前面的条件都不满足那么将设置它的中断状态
 */
public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();   // 检查权限

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // 它是一个native方法, Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
复制代码

得到一个解释说,中断操作只是给线程的一个建议,最终怎么执行看线程本身的状态,那么什么状态做什么事情呢?

  • 若线程被中断前,如果该线程处于非阻塞状态(未调用过wait,sleep,join方法),那么该线程的中断状态将被设为true, 除此之外,不会发生任何事。
  • 若线程被中断前,该线程处于阻塞状态(调用了wait,sleep,join方法),那么该线程将会立即从阻塞状态中退出,并抛出一个InterruptedException异常,同时,该线程的中断状态被设为false, 除此之外,不会发生任何事。

查看wait, sleep, join方法源码,验证上面的第2点:

 /** @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;

 /** @throws  InterruptedException if any thread interrupted the
 *             current thread before or while the current thread
 *             was waiting for a notification.  The <i>interrupted
 *             status</i> of the current thread is cleared when
 *             this exception is thrown.
 * @see        java.lang.Object#notify()
 * @see        java.lang.Object#notifyAll()
 */
public final native void wait(long timeout) throws InterruptedException;

 /** @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 final void join() throws InterruptedException {
    join(0);
}
复制代码

通过注释可以看到这三个方法都会去检查中断状态,随时抛出中断异常。native method属于本地方法了,如果想看内部的实现细节,请各位同为结合hotspot源码对比阅读,这里就不细说了。

所以说,Synchronized锁此时为轻量级锁或重量级锁,此时等待线程是在自旋运行或者已经是重量级锁导致的阻塞状态了(非调用了wait,sleep,join等方法的阻塞),只把中断状态设为true,没有抛出异常真正中断。

对比ReentrantLock

那为什么ReentrantLock可中断呢(未获取到锁也可中断),但是必须使用ReentrantLock.lockInterruptibly()来获取锁,使用ReentrantLock.lock()方法不可中断。 来看看ReentrantLock.lockInterruptibly()源码:

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);  // 调用可中断的获取锁方法
    }

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())    // 获取锁时检查中断状态
            // 显式抛中断异常
            throw new InterruptedException();
        if (!tryAcquire(arg))   // 获取不到锁,执行doAcquireInterruptibly
            doAcquireInterruptibly(arg);
    }


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;  // 应用置null,便于GC
                    failed = false;
                    // 结束自旋
                    return;
                }
                // 检查是否阻塞线程 && 检查中断状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 显式抛中断异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }     
复制代码

从源码可以知道,ReentrantLock.lockInterruptibly()首次尝试获取锁之前就会判断是否应该中断,如果没有获取到锁,在自旋等待的时候也会继续判断中断状态。这时lockInterruptibly底层再显式抛错,而不是像Synchronized那样交由线程自己决定是否抛错。当然lockInterruptibly获取到锁之后,也是得交由线程自己决定。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020年04月24日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 验证真伪
  • 深入分析Synchronized
  • 对比ReentrantLock
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档