【JUC(三)】中断与等待唤醒

1. interrupt() 相关方法

interrupt(),interrupted()isinterrupted() 的区别

public void interrupt():

将线程的中断标记设置为 true,并不是真的停止该线程。
如果线程处于  wait()、join()、sleep() 方法的阻塞当中。中断标记会被清除。也就是通过isInterrupted,interrupted 方法返回均为 false。同时抛出一个InterruptedException异常。

public boolean isInterrupted()
判断某个具体线程对象是否被中断,返回的是线程的中断标记(true or false)

public static boolean interrupted()
返回当前线程的中断状态,然后将当前线程的中断状态设为false。
如果线程中断标记为true,只有第一次调用时会返回 true。之后调用都是false。(这个方法会清除线程的中断状态)
  • 三者的使用
public static void interruptMethod03() throws InterruptedException {
    Thread t1 = new Thread(()->{
        // 当要 interrupt 一个正常运行线程时,
        // 线程的打断标记会被赋值为 true ,但是线程不会立刻中断。
        Thread now = Thread.currentThread();
        int i = 0;
        boolean flag = false;
        while(true){
            flag = Thread.interrupted();
            System.out.println(++i);
            if(flag){
                // 可以说明:Thread.interrupted() 是清除打断标记
                System.out.println("t1.interrupt()执行后,第一次调用 Thread.interrupted(). 应该返回true");
                System.out.println("flag: "+ flag);
                System.out.println("t1.interrupt()执行后,第二次调用 Thread.interrupted(). 应该返回false");
                System.out.println("Thread.interrupted(): "+Thread.interrupted());

                System.out.println();

                System.out.println("再一次调用 t1.interrupt() 之后");
                now.interrupt();
                System.out.println("now.isInterrupted()【不会将打断状态设置为false】. 应该返回true");
                System.out.println("now.isInterrupted(): "+now.isInterrupted());

                System.out.println("Thread.interrupted()【将打断状态设置为false】.当前应该返回true");
                System.out.println("Thread.interrupted(): "+Thread.interrupted());
                System.out.println("经过Thread.interrupted()对打断状态的清除,now.isInterrupted()应该返回false,now.isInterrupted(): "
                                   +now.isInterrupted());
                // 当前线程已经被打断(也即其他线程调用了当前线程的interrupt方法)
                System.out.println(now.getName() + " 线程被打断.....");
                break;
            }
        }
    },"t1");
    t1.start();
    Thread.sleep(100);
    t1.interrupt();
}

通过上面的例子可以了解到:

在Java中没有办法立即停止一条线程(处于阻塞状态除外),然而停止线程却显得尤为重要,如取消一个耗时操作。

因此,Java提供了一种用于停止线程的协商机制——中断

中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。

比如线程A调用了线程Bpublic void interrupt():方法。线程B只能在 run 方法中使用 isInterruptedinterrupted 方法的返回值确定是否中断,然后自行编写中断逻辑。

  • 阻塞线程被中断的情况
public static void main(String[] args) {
    Thread t1 =  new Thread(()->{
        while(true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+"\t"+
                                   "中断标志位:"+Thread.currentThread().isInterrupted()+"程序终止");
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
                // 加了这个,程序可以终止,只会爆异常
                // 如若不在异常处理部分加入 interrupt,程序会一直报异常。
                // 因为 处于阻塞的线程被 interrupt 后,会将线程中断标记设置为false。
                // 这样就导致 Thread.currentThread().isInterrupted() 返回false,程序无法中断
                Thread.currentThread().interrupt();
            }
            System.out.println("-----hello InterruptDemo03");
        }
    },"t1");
    t1.start();
    try {
        TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
    new Thread(() -> t1.interrupt()).start();
}

2. LockSupport

LockSupport是用来创建锁其他同步类基本线程阻塞原语,其中park()unpack()而作用分别是阻塞线程解除阻塞线程

3. 线程的等待唤醒机制

  • 方式一:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  • 方式二:使用JUC包中的Conditionawait()方法让线程等待,使用signal()方法唤醒线程
  • 方式三:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

3.1 Object中wait和notify

  • waitnotify需要成对出现,且需要在同步代码块或同步方法中使用。
  • 要先 wait,后 notify
public static void main(String[] args) {
    Object objectLock = new Object();

    new Thread(()->{
        synchronized(objectLock){
            System.out.println(Thread.currentThread().getName()+" come in 。。。");
            try {
                objectLock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" 被唤醒。。。");
        }
    },"Thread 01").start();

    new Thread(()->{
        synchronized(objectLock){
            System.out.println(Thread.currentThread().getName()+" come in 。。。");
            try {
                objectLock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" 被唤醒。。。");
        }
    },"Thread 03").start();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    new Thread(()->{
        synchronized(objectLock){
            //  如果只有两个线程,一个等待,一个唤醒。就调用notify
            // objectLock.notify();
            // 如果有多个线程等待,就需要调用 notifyAll。 一次唤醒全部等待线程
            objectLock.notifyAll();
            System.out.println(Thread.currentThread().getName()+" 唤醒通知已经发送。。。");
        }
    },"Thread 02").start();
}

3.2 Condition中await和signal

  • Condition中的线程等待和唤醒方法,需要先获取锁
  • 一定要先await后signal,不要反了
  • Condition可以拥有多个,这样面对不同的场景,可以创建不同的 condition。解决 Object对象中多个wait只能notifyAll的情况。
// 方式二:使用`JUC`包中的`Condition`的`await()`方法让线程等待,使用`signal()`方法唤醒线程
public static void awaitAndSignal(){
    Lock lock = new ReentrantLock();
    // 可以针对不同的业务拥有不同的 condition,等待唤醒可以达到具体某个线程的粒度。
    Condition condition = lock.newCondition();
    Condition conditionThread03 = lock.newCondition();

    new Thread(()->{
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t -----------come in");
            condition.await();
            System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    },"Thread 01").start();

    new Thread(()->{
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t -----------come in");
            conditionThread03.await();
            System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    },"Thread 03").start();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    new Thread(()->{
        lock.lock();
        try {
            condition.signal();
            conditionThread03.signal();
            System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
        } finally {
            lock.unlock();
        }
    },"Thread 02").start();
}

3.3 Object和Condition使用的限制条件

  • wait 与 notify 必须在 synchronized 快中使用,否则会报异常。
  • await 与 signal 需要配合 lock 使用。
  • 二者的等待与唤醒的顺序是不能错的。

3.4 LockSupport类中的park等待和unpark唤醒

  • LockSupport 使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能
  • 每个线程都有一个许可(permit),且累加上限为1(也就是说,要么有一个许可,要么没有许可)。
  • 当程序调用 LockSupport.park();时,如果当前线程没有许可,就进行阻塞,有许可就放行。
  • LockSupport.unpark(t1);t1 线程一个许可(permit),

例子:

// 方式三:`LockSupport`类可以`阻塞当前线程`以及`唤醒指定被阻塞的线程`
// LockSupport 使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能.每个线程都有一个许可(Permit),许可证只能有一个,累加上限是1。
public static void parkAndUnpark(){
    /**
      * t1	 -----------come in
       * t2	 ----------发出通知
       * t1	 ----------被唤醒
       */
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "\t -----------come in");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
    }, "t1");
    t1.start();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    new Thread(() -> {
        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
    }, "t2").start();
}

例子:先调用 unpark , 在调用 park。不会报异常,也不会阻塞。

/*
    先调用 unpark 后调用 park
     */
public static void parkAndUnpark02(){
    	/**
         * t1	 -----------come in
         * t2	 ----------发出通知
         * t1	 ----------被唤醒
         */
    Thread t1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "\t -----------come in");
        System.out.println(Thread.currentThread().getName() + "\t ----------unpark");
        LockSupport.unpark(Thread.currentThread());

        System.out.println(Thread.currentThread().getName() + "\t ----------park");
        // 上面已经 unpark ,得到了一个 permit。 
        // 所以调用一次 park 不会产生阻塞。
        // 但是如果调用2两次及以上,就会产生阻塞。应该 permit 不累加,最大为1。
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "不会阻塞");
    }, "t1");
    t1.start();
}
  • 其他方法
权限方法签名与解释
static ObjectgetBlocker(Thread t)Returns the blocker object supplied to the most recent invocation of a park method that has not yet unblocked, or null if not blocked.
static voidpark()Disables the current thread for thread scheduling purposes unless the permit is available.
static voidpark(Object blocker)Disables the current thread for thread scheduling purposes unless the permit is available.
static voidparkNanos(long nanos)Disables the current thread for thread scheduling purposes, for up to the specified waiting time, unless the permit is available.
static voidparkNanos(Object blocker, long nanos)Disables the current thread for thread scheduling purposes, for up to the specified waiting time, unless the permit is available.
static voidparkUntil(long deadline)Disables the current thread for thread scheduling purposes, until the specified deadline, unless the permit is available.
static voidparkUntil(Object blocker, long deadline)Disables the current thread for thread scheduling purposes, until the specified deadline, unless the permit is available.
static voidunpark(Thread thread)Makes available the permit for the given thread, if it was not already available.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值