【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)

目录

LockSupport与线程中断

线程中断机制

什么是中断机制?

与中断相关的3个API

如何停止中断运行中的线程?

当前线程的中断标识为true,是不是线程就会立刻停止?

如何理解静态方法Thread.interrupted()

LockSupport是什么

线程等待和唤醒机制

3种让线程等待唤醒的方法

Object类中的wait()和notify()方法实现线程的等待和唤醒

Condition接口中的await()后signal()方法实现线程的等待和唤醒

上述两个对象Object和Condition使用的限制条件

LockSupport类中的park()等待和unpack()唤醒

总结


LockSupport与线程中断

线程中断机制

什么是中断机制?

首先,一个线程不应该由其他线程强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运(所以,Thread的stop()、suspend()、resume()都已经废弃了)

其次,在Java中没有办法立即停止一条线程,然而停止线程又显得那么重要(比如需要取消一个耗时/错误操作)。因此,Java提供了一种用于停止线程的协商机制——中断,也即中断标识协商机制

中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完成完全要求程序员自己实现。若要中断一个线程,需要手动调用该线程的interrupt()方法,该方法也仅仅是将线程对象的中断标识设成true,接着按自己的需要,写代码不断监测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竟该做什么需要自己写代码实现。

每个线程对象中都有一个中断标识位,用于表示线程是否被中断:该标识位为true标识中断,为false表示未中断。通过调用线程对象的interrupt方法将该线程的标识位设为true,可以在别的线程中带调用,也可以在自己的线程中调用。

与中断相关的3个API
  • public void interrupt():设置线程的中断状态为true,发起一个协商而不会立刻停止线程
  • public static boolean isInterrupted():通过检查中断标识位判断线程是否被中断并清除当前中断状态(返回当前线程的中断状态+重置中断状态为false)
  • public boolean isInterrupted():通过检查中断标识位判断当前线程是否被中断
如何停止中断运行中的线程?
  • 通过volatile变量实现(线程间的可见性)
  • 通过AtomicBoolean实现

执行结果如下图:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第1张图片

  • 通过Thread类自带的中断API实例方法实现 在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑stop线程
    • interrupt() 中断线程

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第2张图片

我们可以看到interrupt0()是一个native方法

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第3张图片

除非线程正在中断(始终允许),否则将带调用此线程的checkAccess()方法(可能还会导致抛出SecurityException)

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第4张图片

public class InterruptDemo {
    static volatile boolean isStop = false;

    public static void main(String[] args) {

        Thread tA = new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.printf(Thread.currentThread().getName() + " isStop 被修改为true!");
                    break;
                }
                System.out.println("-------------------->");
            }
        }, "tA");
        tA.start();

        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            isStop = true;
        }, "B").start();

    }
}

执行结果如下图:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第5张图片

public class InterruptDemo {
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

    public static void main(String[] args) {

        Thread tA = new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.printf(Thread.currentThread().getName() + " atomicBoolean 被修改为true!");
                    break;
                }
                System.out.println("-------------------->");
            }
        }, "tA");
        tA.start();

        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            atomicBoolean.set(true);
        }, "B").start();

    }
}
package com.aqin.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @Description
 * @Author aqin1012 AQin.
 * @Date 10/9/23 11:29 AM
 * @Version 1.0
 */
public class InterruptDemo {
    public static void main(String[] args) {
        Thread tA = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.printf(Thread.currentThread().getName() + " isInterrupted() 被修改为true!程序停止!");
                    break;
                }
                System.out.println("-------------------->");
            }
        }, "tA");
        tA.start();

        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            tA.interrupt();
        }, "B").start();

    }
}

执行效果如下图:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第6张图片

当前线程的中断标识为true,是不是线程就会立刻停止?

不会,具体来说,对一个线程,调用interrupt()时,如果线程处于正常活动状态,那么会将该线程的中断标志设置为true(仅此而已),被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不是真正的中断线程,需要被调用的线程自己进行配合才行。如果线程处于被阻塞状态(如sleep、wait、join等状态),在别的线程中调用当前线程对象的interrupt()方法时,那么线程将会立即退出被阻塞状态,并抛出一个InterruptedException异常。

如何理解静态方法Thread.interrupted()

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第7张图片

这个方法是判断线程是否中断并清除当前中断状态的,简单来说就是“复位”。

主要做了两件事:

  1. 返回当前线程的中断状态,测试当前线程是否已经被中断
  2. 将当前线程的中断状态清零并重新设置为false,清除线程的中断状态

那么问题来了,这个静态方法interrupted()跟实例方法isInterrupted()有什么区别呢?

我们举个简单的例子对比下执行结果

静态方法interrupted()

执行结果如下:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第8张图片

实例方法isInterrupted()

执行结果如下

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第9张图片

来看它俩的源码对比

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第10张图片

可以看到其实它们调用的是同一个方法,只不过,在对于是否需要清理这个参数的传递上,静态方法传递的是true,而实例方法传递的是false,简单来讲就是多了一步“复位”操作。因此,才会出现上面两段示例代码执行的结果不一致,在当前线程未执行中断方法interrupt()时,当前线程的中断标识位本身就是初始值false,因此连续调用两次静态方法或者实例方法的执行结果都是false;当当前线程执行了中断方法interrupt()时,第一次调用静态方法和实例方法的返回值同样都是true,但是此时静态方法多做了一步“复位”操作,把当前线程的中断标识位重置回了初始值false,而实例方法则没有这步操作,因此,当第二次调用时,实例方法的示例中当前线程的中断标识位仍然是true,因此仍然返回true,而静态方法的示例代码中当前线程的中断标识位已经被重置回了false,于是就返回了false。

LockSupport是什么

LockSupport是java.util.concurrent.locks中的一个类,用于创建锁和其他同步类的基本线程阻塞原语

线程等待和唤醒机制

3种让线程等待唤醒的方法
  1. 使用Object中的wait()方法让线程等待,使用notify()方法唤醒线程
  2. 使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
  3. 使用LockSupport类中的park()和unpark()方法阻塞当前线程以及唤醒指定被阻塞的线程
Object类中的wait()和notify()方法实现线程的等待和唤醒

示例代码:

public static void main(String[] args) {
    Object objectLock = new Object();
    new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (objectLock) {
            System.out.println(Thread.currentThread().getName() + " 进入--->");
            try {
                objectLock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");
        }
    }, "A").start();

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

    new Thread(() -> {
        synchronized (objectLock) {
            objectLock.notify();
        }
        System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");
    }, "B").start();
}

执行结果如下:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第11张图片

问题:

  • 必须要先持有锁,否则会报错

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第12张图片

  • 必须先wait()再notify(),否则会卡死

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第13张图片

Condition接口中的await()后signal()方法实现线程的等待和唤醒

示例代码:

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    new Thread(() -> {
        try { TimeUnit.SECONDS.sleep(1); } catch ( InterruptedException e) { e.printStackTrace();}

        lock.lock();
        System.out.println(Thread.currentThread().getName() + " 进入--->");
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");

    }, "A").start();

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

    new Thread(() -> {
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");
    }, "B").start();
 }

执行结果如下:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第14张图片

问题:

  • 必须要先持有锁,否则会报错

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第15张图片

在lock、unlock对里面才能正确调用condition中线程等待和唤醒的方法。

  • 必须先await()再signal(),否则会卡死

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第16张图片

上述两个对象Object和Condition使用的限制条件
  • 线程先要获得并持有锁,必须在锁块(synchronized/lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒
LockSupport类中的park()等待和unpack()唤醒

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每一个线程都有一个Permit(许可),但与Semaphore不同的是,许可的累加上限是1(最多一个许可)。

使用示例:

public static void main(String[] args) {
    Thread A = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 进入--->");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + " 被唤醒^ ^");

    }, "A");
    A.start();

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

    new Thread(() -> {
        LockSupport.unpark(A);
        System.out.println(Thread.currentThread().getName() + " 发出唤醒通知( ̄∇ ̄)/");
    }, "B").start();
}

执行结果如下:

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第17张图片

可以看到,使用LockSupport进行线程阻塞/唤醒不需要在锁对块(synchronized/lock)中,所以上面Object类和Condition接口的第一个问题解决了,然后我们看看第二个问题:加锁/解锁的先后顺序。

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第18张图片

如上图可以看出,park()和unpack()执行的先后顺序不会影响结果。

总结

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport是一个线程阻塞工具,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞后也有对应的唤醒方法,归根结底,LockSupport调用的是Unsafe中的native代码。

【JUC】Java并发编程从挖坑到入土全解(4-一文讲通LockSupport与线程中断->长图预警)_第19张图片

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,每个使用LockSupport的线程都一个Permit(许可)与LockSupport相关联,每一个线程都都一个相关的Permit,并且最多一个,重复调用unpark()也不会积累Permit。

换句话讲,线程阻塞需要消耗凭证,并且这个凭证最多1个

当调用park()方法时:

  • 如果有Permit,会消耗这个Permit,然后正常退出
  • 如果没有Permit,会阻塞当前线程等待获取凭证

当调用unpark()方法时:

  • 会增加一个Permit
  • 调用多次也只会有一个Permit(不累加)

最后,我们思考2个问题:

  1. 为什么LockSupport提供park()和unpark()方法可以突破Object和Condition的调用先后顺序的限制? 因为调用unpark()会获得1个Permit,之后再调用park()会消耗这个Permit,park()和unpark()方法的执行顺序不会影响线程的唤醒,即便先发放了Permit仍然不会阻塞线程。
  2. 唤醒两次(调用两次park()方法)后阻塞两次(调用两次unpark)方法),会发生什么情况? 线程会被阻塞,因为Permit不会累加,即使调用了两次unpark(),仍然只会有一个Permit,后面接着的调用两次park(),在调用第一次park()时,就会把这个Permit消耗掉,第二次调用时就没有Permit了,因而会被阻塞。

搞定( ̄∇ ̄)/~~~~~~~~~~

你可能感兴趣的:(JUC,Java,java,JUC,LockSupport,线程,线程中断,线程阻塞,线程唤醒)