JUC并发编程与源码分析笔记05-LockSupport与线程中断

内容简介

LockSupport

LockSupport是java.util.concurrent.locks包下的一个类,可以理解成是对Lock类的扩展。

线程中断机制

java.lang.Thrread里有三个方法:interrupt()interrupted()isInterrupted()

线程中断机制

什么是中断机制

一个线程不应该由其他线程来强制中断或停止,而是应该山线程自己自行停止,自己来决定自己的命运。所以,Thread.stop()Thread.suspend()Thread.resume()都已经被废弃了。
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制―—中断,也即中断标识协商机制。
中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt()方法,该方法也仅仅是将线程对象的中断标识设成true,接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,
此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断,该标识位为true表示中断,为false表示未中断。通过调用线程对象的interrupt()方法将该线程的标识位设为true,可以在别的线程中调用,也可以在自己的线程中调用。

中断的相关三大API方法说明

public void interrupt():设置线程的中断状态为true,发起一个协商,不会立刻停止线程。
public static boolean interrupted():判断线程是否被中断并清除当前中断状态。这个方法做了两件事:1.返回当前线程的中断状态,测试当前线程是否已被中断;2.将当前线程的中断状态清零并设置成false,清除线程的中断状态。
如果连续两次调用此方法,则第二次调用将返回false ,因为连续调用两次的结果可能不一样。
public boolean isInterrupted():通过检查中断标志位,判断当前线程是否被中断。

大厂面试题中断机制考点

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

通过一个volatile变量实现
public class InterruptDemo {
    static volatile boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!stop) {
                System.out.println(Thread.currentThread().getName() + "正在运行");
            }
        }, "thread1").start();
        Thread.sleep(10);
        new Thread(() -> {
            stop = true;
        }, "thread2").start();
    }
}
通过AtomicBoolean实现
import java.util.concurrent.atomic.AtomicBoolean;

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

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!atomicBoolean.get()) {
                System.out.println(Thread.currentThread().getName() + "正在运行");
            }
        }, "thread1").start();
        Thread.sleep(10);
        new Thread(() -> {
            atomicBoolean.set(true);
        }, "thread2").start();
    }
}
通过Thread类自带的中断api实例方法实现

在需要中断的线程中,不断监听中断状态,一旦发生中断,就执行中断处理业务stop线程。

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + "正在运行");
            }
        }, "thread1");
        thread1.start();
        System.out.println("thread1默认标志位" + thread1.isInterrupted());
        Thread.sleep(10);
        new Thread(thread1::interrupt, "thread2").start();
    }
}

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

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

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 300;i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            System.out.println("thread1调用interrupt()后标志位2:" + Thread.currentThread().isInterrupted());
        }, "thread1");
        thread1.start();
        System.out.println("thread1默认标志位:" + thread1.isInterrupted());
        Thread.sleep(2);
        thread1.interrupt();
        System.out.println("thread1调用interrupt()后标志位1:" + thread1.isInterrupted());
        Thread.sleep(2000);
        System.out.println("thread1调用interrupt()后标志位3:" + thread1.isInterrupted());
    }
}
public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "中断标志位:" + Thread.currentThread().isInterrupted() + "程序停止");
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    // 如果没有这句,thread1可能一直执行,当thread1处于阻塞状态的时候,其他线程调用了thread1.interrupt(),就会抛异常,而且会清空标志位的值
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
                System.out.println("hello world");
            }
        }, "thread1");
        thread1.start();
        Thread.sleep(1000);
        thread1.interrupt();
    }
}

静态方法Thread.interrupted(),谈谈你的理解

判断线程是否被中断并清除当前中断状态。
这个方法做了两件事:
返回当前线程中断状态,测试当前线程是否已被中断
将当前线程中断状态清零并设置为false,清除线程的中断状态

public class InterruptDemo {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        System.out.println(1);
        Thread.currentThread().interrupt();
        System.out.println(2);
        // 因为执行了Thread.currentThread().interrupt();所以返回true,并且Thread.interrupted()还会把中断标志位置为false
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());
    }
}

总结

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
    return isInterrupted(false);
}

查看源码,就会发现isInterrupted()interrupted()最终都调用了private native boolean isInterrupted(boolean ClearInterrupted);方法,不同点是传参不同,也就是说,这次操作是否需要清空标志位。

LockSupport是什么

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

线程等待唤醒机制

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

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

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

public class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        new Thread(() -> {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                try {
                    object.wait();// 当前进程释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "被唤醒");
            }
        }, "thread1").start();
        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (object) {
                object.notify();
                System.out.println(Thread.currentThread().getName() + "发出通知");
            }
        }, "thread2").start();
    }
}

两个异常情况:
如果把上面代码的两个synchronized代码块注释掉,再次运行,程序会报错。结论:wait()notify()方法必须在synchronized里,也就必须持有该对象的锁,才能调用该对象的wait()notify()方法。
尝试改变两个线程wait()notify()的顺序,也就是在thread1里wait之前,加一个sleep,先让thread2执行notify,再让thread1执行wait,程序在走完wait()后,是没法被唤醒的,永远wait下去,程序不能正常退出。结论:必须先调用wait(),再调用notify()

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

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "被唤醒");
        }, "thread1").start();
        Thread.sleep(1000);
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "发出通知");
            } finally {
                lock.unlock();
            }
        }, "thread2").start();
    }
}

两个异常情况:
lock.lock()lock.unlock()注释掉,再次运行,程序会报错。结论:await()signal()的执行,需要先获得锁。
尝试改变await()signal()的执行顺序,同样,也会出现程序不能正常停止的情况。结论:必须先调用await(),再调用signal()

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

  1. 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  2. 必须先等待后唤醒,线程才能被成功唤醒

LockSupport类中的park等待和unpark唤醒

是什么

通过park()unpark(thread)方法实现阻塞和唤醒线程的操作。

主要方法

阻塞:public static void park()
唤醒:public static void unpark(Thread thread)

代码

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "开始执行");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "被唤醒");
        }, "thread1");
        thread1.start();
        Thread.sleep(1000);
        new Thread(() -> {
            LockSupport.unpark(thread1);
            System.out.println(Thread.currentThread().getName() + "发出通知");
        }, "thread2").start();
    }
}

尝试之前的两个异常情况,发现程序可以正常执行。
使用LockSupport后,不需要把阻塞和唤醒放在锁内部,不需要控制阻塞和唤醒的顺序。
对于LockSupport.park()LockSupport.unpark(),可以理解为通行证的意思,park()是上交通行证,unpark()是发放通行证,而且通行证在手里最多只能有一张。
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport是一个线程阻寨工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
LockSupport提供park()unpark()方法实现阻塞线程和解除线程阻塞的过程LockSupport和每个使用它的线程都有一个许可关联。
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark()也不会积累凭证。
线程阻塞需要消耗凭证,这个凭证最多只有一个。
当调用park()方法时,直接消耗掉这个凭证然后正常退出,如果没有凭证,就必须阻塞等待凭证可用。
当调用unpark()方法时,会增加一个凭证,但是凭证最多只能有一个,累计无效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值