博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java并发包源码学习之AQS框架(三)LockSupport和interrupt
阅读量:6734 次
发布时间:2019-06-25

本文共 4884 字,大约阅读时间需要 16 分钟。

接着今天我们来介绍下LockSupport和Java中线程的中断(interrupt)

其实除了LockSupport,Java之初就有Object对象的wait和notify方法可以实现线程的阻塞和唤醒。那么它们的区别 是什么呢?

主要的区别应该说是它们面向的对象不同。阻塞和唤醒是对于线程来说的,LockSupport的park/unpark更符合这个语义,以“线程”作为方法的参数, 语义更清晰,使用起来也更方便。而wait/notify的实现使得“线程”的阻塞/唤醒对线程本身来说是被动的,要准确的控制哪个线程、什么时候阻塞/唤醒很困难, 要不随机唤醒一个线程(notify)要不唤醒所有的(notifyAll)。

wait/notify最典型的例子应该就是生产者/消费者了:

class BoundedBuffer1 {    private int contents;    final Object[] items = new Object[100];    int putptr, takeptr, count;    public synchronized void put(Object x) {        while (count == items.length) {            try {                wait();            } catch (InterruptedException e) {            }        }        items[putptr] = x;        if (++putptr == items.length)            putptr = 0;        ++count;        notifyAll();    }    public synchronized Object take() {        while (count == 0) {            try {                wait();            } catch (InterruptedException e) {            }        }        Object x = items[takeptr];        if (++takeptr == items.length)            takeptr = 0;        --count;        notifyAll();        return x;    }    public static class Producer implements Runnable {        private BoundedBuffer1 q;        Producer(BoundedBuffer1 q) {            this.q = q;            new Thread(this, "Producer").start();        }        int i = 0;        public void run() {            int i = 0;            while (true) {                q.put(i++);            }        }    }    public static class Consumer implements Runnable {        private BoundedBuffer1 q;        Consumer(BoundedBuffer1 q) {            this.q = q;            new Thread(this, "Consumer").start();        }        public void run() {            while (true) {                System.out.println(q.take());            }        }    }    public static void main(String[] args) throws InterruptedException {        final BoundedBuffer1 buffer = new BoundedBuffer1();        new Thread(new Producer(buffer)).start();        new Thread(new Consumer(buffer)).start();    }}
View Code

上面的例子中有一点需要知道,在调用对象的wait之前当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行。

//wait会先释放当前线程拥有的监视器obj.wait();//会re-acquire监视器

LockSupport并不需要获取对象的监视器。LockSupport机制是每次unpark给线程1个“许可”——最多只能是1,而park则相反,如果当前 线程有许可,那么park方法会消耗1个并返回,否则会阻塞线程直到线程重新获得许可,在线程启动之前调用park/unpark方法没有任何效果。

// 1次unpark给线程1个许可LockSupport.unpark(Thread.currentThread());// 如果线程非阻塞重复调用没有任何效果LockSupport.unpark(Thread.currentThread());// 消耗1个许可LockSupport.park(Thread.currentThread());// 阻塞LockSupport.park(Thread.currentThread());

因为它们本身的实现机制不一样,所以它们之间没有交集,也就是说LockSupport阻塞的线程,notify/notifyAll没法唤醒。

实际上现在很少能看到直接用wait/notify的代码了,即使生产者/消费者也基本都会用LockCondition来实现,我会在后面《Java并发包源码学习之AQS框架(五)ConditionObject源码分析》 文章中再回头看这个例子。

总结下LockSupportpark/unparkObjectwait/notify

  • 面向的对象不同;
  • 跟Object的wait/notify不同LockSupport的park/unpark不需要获取对象的监视器;
  • 实现的机制不同,因此两者没有交集。

虽然两者用法不同,但是有一点,LockSupport的park和Object的wait一样也能响应中断。

public static void main(String[] args) throws InterruptedException {    final Thread t = new Thread(new Runnable() {        @Override        public void run() {            LockSupport.park();            System.out.println("thread " + Thread.currentThread().getId() + " awake!");        }    });    t.start();    Thread.sleep(3000);    // 2. 中断    t.interrupt();}
thread 9 awake!

在我之前的“如何正确停止一个线程”有介绍过Thread.interrupt()

Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

LockSupport.park()也能响应中断信号,但是跟Thread.sleep()不同的是它不会抛出InterruptedException, 那怎么知道线程是被unpark还是被中断的呢,这就依赖线程的interrupted status,如果线程是被中断退出阻塞的那么该值被设置为true, 通过Thread的interruptedisInterrupted方法都能获取该值,两个方法的区别是interrupted获取后会Clear,也就是将interrupted status重新置为false。

AQS和Java线程池中都大量用到了中断,主要的作用是唤醒线程、取消任务和清理(如ThreadPoolExecutor的shutdown方法),AQS中的acquire方法也有中断和不可中断两种。 其中对于InterruptedException如何处理最重要的一个原则就是Don't swallow interrupts,一般两种方法:

  • 继续设置interrupted status
  • 抛出新的InterruptedException
try {    ………} catch (InterruptedException e) {    // Restore the interrupted status    Thread.currentThread().interrupt();    // or thow a new    //throw new InterruptedException();}

AQS的acquire就用到了第一种方法。

关于InterruptedException处理的最佳实践可以看。

最后按照惯例做下引申。上面BoundedBuffer1类的puttake方法中的wait为什么要放在一个while循环里呢? 你如果去看Object.wait()方法的Javadoc的话会发现官方也是建议下面这样的用法:

synchronized (obj) {    while (
) …… obj.wait(); ……}

StackOverflow上有一个问题里一个叫解释的比较清楚,有兴趣的可以看下。 简单来说因为:

wait前会释放监视器,被唤醒后又要重新获取,这瞬间可能有其他线程刚好先获取到了监视器,从而导致状态发生了变化, 这时候用while循环来再判断一下条件(比如队列是否为空)来避免不必要或有问题的操作。 这种机制还可以用来处理伪唤醒(spurious wakeup),所谓伪唤醒就是no reason wakeup,对于LockSupport.park()来说就是除了unparkinterrupt之外的原因。

LockSupport也会有同样的问题,所以看AQS的源码会发现很多地方都有这种re-check的思路,我们下一篇文就来看下AbstractQueuedSynchronizer类的源码。

 

转载于:https://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-locksupport-and-thread-interrupt.html

你可能感兴趣的文章
1.06 在WHERE子句中引用取别名的列
查看>>
JBPM流程部署之流程版本升级
查看>>
Java面向对象概述
查看>>
Hello 畅连·西瓜 帮助与更新
查看>>
第十三周项目2-成绩处理
查看>>
html 复制 有时不显示样式
查看>>
怎么写测试策略
查看>>
2018-2019-1 20165231 《信息安全系统设计基础》第四周学习总结
查看>>
jar包的一天
查看>>
python random模块
查看>>
发布使用了stage3D功能的Air for Android项目到手机上
查看>>
15. 利用ajax jquery 上传文件
查看>>
4.类与结构
查看>>
smartUpload上传文件组件
查看>>
Android系统移植与调试之------->build.prop文件详细赏析
查看>>
SOUI更新到2.0
查看>>
条件编译
查看>>
Linux命令——mesg
查看>>
Argus
查看>>
自定义UIButton
查看>>