LockSupport与线程中断

1.什么是中断机制?

首先,一个线程是否应该中断或停止,不应该由其他线程决定,而应该由线程自身决定,所以Thread.stop(),Thread.suspend(),Thread.resume()都已经被废弃了。其次,在Java中没有办法立刻停止一个线程,但是有些情况,比如需要取消一些耗时耗资源的操作确显得非常有必要,于是Java提供了一种用于停止线程的协商机制-中断,也即中断标识协商机制。
中断 只是一种协商机制,Java并没有给中断增加任何语法,中断的过程完全需要程序员自己来实现。
如要中断一个线程,你需要手动调用该线程的interrupt()方法,而该方法也仅是将线程对象的中断标识设置为true,接下来,你需要写代码不断检测当前线程的标识位,如果为true,代表别的线程请求这条线程中断,此时需要做什么,需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断。true表示已中断,false表示未中断。通过调用线程对象的interrupt()方法将线程的标识位设置为true。可以在别的线程中调用,也可以在自己线程中调用。

2.中断常见方法

2.1 public void interrupt()

	将线程中断状态设置为true,发起一个协商而不会立即停止线程。

2.2 public static boolean interrupted()

	返回当前线程的中断状态,然后清除当前线程的中断状态,即将中断状态重新设置为false。

2.3 public boolean isInterrupted()

	返回当前线程的中断状态。

3.大厂面试题中断机制考点

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

3.1.1 通过volatile变量实现

代码如下:

package multiThreads;

import java.util.concurrent.TimeUnit;

public class InterruptTest {
    static volatile boolean isStop = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println(Thread.currentThread().getName() + "结束-------");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "还未结束-------");
            }
        }, "a").start();


        TimeUnit.MILLISECONDS.sleep(5L);

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

    }
}

结果如下:
在这里插入图片描述

3.1.2 通过AtoicBoolean实现

代码如下:

package multiThreads;

import com.sun.org.apache.bcel.internal.generic.FADD;

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

public class InterruptTest {
    static volatile boolean isStop = false;

    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

    public static void main(String[] args) throws InterruptedException {
//        m1Volatile();
        m2AtomicBoolean();

    }

    private static void m2AtomicBoolean() throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println(Thread.currentThread().getName() + "结束-------");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "还未结束-------");
            }
        }, "a").start();


        TimeUnit.MILLISECONDS.sleep(5L);

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

    private static void m1Volatile() throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println(Thread.currentThread().getName() + "结束-------");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "还未结束-------");
            }
        }, "a").start();


        TimeUnit.MILLISECONDS.sleep(5L);

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

结果如下:
在这里插入图片描述

3.1.3 通过Thread类自带的中断api实例方法实现

代码如下:

package multiThreads;

import java.util.concurrent.TimeUnit;

public class InterruptTest2 {
    public static void main(String[] args) throws InterruptedException {


        Thread thread = new Thread(() -> {
            while (true) {
                Thread currentThread = Thread.currentThread();
                if (currentThread.isInterrupted()) {
                    System.out.println(currentThread.getName() + "被中断,结束时间:" + System.currentTimeMillis());
                    break;
                }
                System.out.println(currentThread.getName() + "执行----------");
            }
        }, "a");

        thread.start();
        TimeUnit.MILLISECONDS.sleep(10L);
        thread.interrupt();
        System.out.println("main打断时间:" + System.currentTimeMillis());

    }
}

结果如下:
在这里插入图片描述

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

具体来说,当一个线程调用interrupt()方法的时候:
1.如果线程处于正常活动状态,那么会将中断标识设置为true,仅此而已,被设置中断标识的线程会继续正常运行,不受影响。所以,interrupt()只是协商,并不能立刻直接中断某个线程,需要被调用的线程自己进行配合才行。
2.如果线程处于阻塞状态,例如sleep(),wait(),join()等,那么线程会立刻退出阻塞状态,并抛出InterruptedException,且会将线程中断标识设置为false。

代码示例:

package multiThreads;

import java.util.concurrent.TimeUnit;

public class InterruptTest3 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println(i);
            }
            System.out.println(Thread.currentThread().getName() + "的中断状态是:" + Thread.currentThread().isInterrupted());
        }, "a");

        thread.start();

        TimeUnit.MILLISECONDS.sleep(1L);

        thread.interrupt();
        System.out.println(thread.getName() + "的中断状态是:" + thread.isInterrupted() + "----------");
    }
}

运行结果:
在这里插入图片描述
在这里插入图片描述
可以看到线程a调用interrupt()后,中断状态是true,但是线程还是依旧执行for循环。

3.3 对于静态方法Thread.interrupted()的理解?

1.返回当前线程的中断状态
2.将当前线程的中断状态设置为false

4 LockSupport

LockSupport是java.util.concurrent.locks.LockSupport中的一个类,用于创建锁和其他同步类的基本线程阻塞原语。
常见方法:
1.public static void park() :阻塞线程
2.public static void unpark(Thread thread) :解除阻塞线程

4.1 三种实现线程等待和唤醒的方法

4.1.1 Object的wait()和notify()

代码如下:

package multiThreads;

import java.util.concurrent.TimeUnit;

public class WaitTest {
    public static void main(String[] args) throws InterruptedException {
        Object objectLock = new Object();
        new Thread(() -> {
            synchronized (objectLock) {
                System.out.println("come in--------");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("被唤醒-----");
            }

        }, "a").start();

        TimeUnit.SECONDS.sleep(1L);
        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();
                try {
                    TimeUnit.SECONDS.sleep(2L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("发出通知");
            }
        }, "b").start();
    }
}

运行结果如下:
在这里插入图片描述
结果解析:线程A中wait()方法会导致线程A进入阻塞状态,同时释放锁。另一个线程B获取锁后,执行notify()方法,会唤醒在此监视器(Monitor,此处指objectLock的Monitor)上等待的某一个线程,此处就是线程A,线程A被唤醒后,并不会立刻获取到锁,需要等线程B执行完毕,且释放锁之后,线程A才能重新尝试获取锁,因为没有其他线程争抢,所以线程B执行完毕释放锁之后,线程A自然就能获取到锁,继续执行后续代码。

注意点:
1.当使用object.wait()的时候,当前线程必须是锁对象的Monitor的持有者,否则会抛出 IllegalMonitorStateException异常
代码如下:

package multiThreads;

import java.util.concurrent.TimeUnit;

public class WaitTest {
    public static void main(String[] args) throws InterruptedException {
        Object objectLock = new Object();
        new Thread(() -> {
            //synchronized (objectLock) {
                System.out.println("come in--------");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("被唤醒-----");
           // }

        }, "a").start();

        TimeUnit.SECONDS.sleep(1L);
        new Thread(() -> {
           // synchronized (objectLock) {
                objectLock.notify();
                try {
                    TimeUnit.SECONDS.sleep(2L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("发出通知");
            //}
        }, "b").start();
    }
}

结果如下:
在这里插入图片描述
2.注意wait()需要在notify()的前面执行,否则wait()的线程无人唤醒,将会一直等待下去。
代码如下:

package multiThreads;

import java.util.concurrent.TimeUnit;

public class WaitTest {
    public static void main(String[] args) throws InterruptedException {
        Object objectLock = new Object();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (objectLock) {

                System.out.println("come in--------");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("被唤醒-----");
            }

        }, "a").start();

        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();

                System.out.println("发出通知");
            }
        }, "b").start();
    }
}

运行结果:
在这里插入图片描述

4.1.2 Condition的await()和signal()

代码如下:

package multiThreads;

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

public class WaitTest {
    public static void main(String[] args) throws InterruptedException {
        //waitNotify();
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("a come in--");
                condition.await();
                System.out.println("a resume----");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.lock();
            }
        }, "a").start();


        TimeUnit.SECONDS.sleep(1L);


        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("b come in");
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "b").start();

    }

   
}

运行结果如下:
在这里插入图片描述
Condition的await()等价于Object的wait(),都是让当前线程释放锁,并进入阻塞状态。
Condition的signal()等价于Object的notify(),都是唤醒在当前锁上等待的阻塞线程。

注意点:Condition中的await()和signal()执行的时候都需要保证当前线程获取了锁,其次注意await()之后一定要有线程来signal()进行唤醒。

4.1.3 使用LockSupport的park()和unpark()来实现
LockSupport.park()方法:

在这里插入图片描述
如果没有许可证(permit)会默认阻塞线程,直到别的线程给当前线程发放许可证(permit),当前线程才会被唤醒。

LockSupport.unpark()方法:

在这里插入图片描述
给予入参Thread许可证(permit),之前阻塞的LockSupport.park()方法会立刻返回。

示例代码如下:

package multiThreads;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("a come in");
            LockSupport.park();
            System.out.println("a is invoked");
        }, "a");
        thread.start();

        TimeUnit.SECONDS.sleep(1L);

        new Thread(() -> {
            LockSupport.unpark(thread);
            System.out.println("b invoke a");
        }, "b").start();
    }
}

运行结果:
在这里插入图片描述
可以看到unpark()和park()方法无需获取锁即可执行。

但是如果先执行unpark(),然后执行park()呢?会导致线程a无法被唤醒,一直等待吗?代码验证,如下:

package multiThreads;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("a come in");
            LockSupport.park();
            System.out.println("a is invoked");
        }, "a");
        thread.start();

        TimeUnit.SECONDS.sleep(1L);

        new Thread(() -> {
            LockSupport.unpark(thread);
            System.out.println("b invoke a");
        }, "b").start();
    }
}

运行结果:
在这里插入图片描述
可以看到,依旧可以成功唤醒。park()和unpark()没有执行先后顺序要求。这是为什么呢?请看如下代码:

package multiThreads;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("a come in,当前时间:" + System.currentTimeMillis());
            LockSupport.park();
            System.out.println("a is invoked,当前时间:" + System.currentTimeMillis());
        }, "a");
        thread.start();

        TimeUnit.SECONDS.sleep(1L);

        new Thread(() -> {
            LockSupport.unpark(thread);
            System.out.println("b invoke a,当前时间:" + System.currentTimeMillis());
        }, "b").start();
    }
}

运行结果:
在这里插入图片描述
可以看到,a come in语句和 a is invoked语句的执行时间完全一致,也就是说这两行代码之间的LockSupport.park();没有生效。这是因为,先执行了下面的unpark()方法,导致这个park()失效了。类似于高速公路,提前买好了通行证unpark,到闸机处直接放行了,没有park拦截了。注意,虽然park()和unpark()没有了顺序要求,但是还是得成双成对的使用,否则某个park没有unpark会导致线程一直阻塞哦。

还有一个注意点,就是unpark获取的许可证(permit)对于被唤醒线程来说,最多只会有一个,即使你unpark()多次,被唤醒线程也最多持有一个许可证(permit)。但是每次从park()中被唤醒,都需要消耗一个permit。所以如果线程A park()两次,同时线程B unpark()两次,那么线程A第一次的park()可以被唤醒,但是第二次的park()无法被唤醒,这点需要注意。示例代码如下:

package multiThreads;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("a come in,当前时间:" + System.currentTimeMillis());
            LockSupport.park();
            LockSupport.park();
            System.out.println("a is invoked,当前时间:" + System.currentTimeMillis());
        }, "a");
        thread.start();

        TimeUnit.SECONDS.sleep(1L);

        new Thread(() -> {
            LockSupport.unpark(thread);
            LockSupport.unpark(thread);
            System.out.println("b invoke a,当前时间:" + System.currentTimeMillis());
        }, "b").start();
    }
}

运行结果:
在这里插入图片描述

面试题:
1.为什么可以突破wait()和notify()的顺序限制?
因为unpark()会让被唤醒线程获取permit,而唤醒因park()被阻塞的线程需要消耗permit。提前执行unpark()让被唤醒线程获得了一个permit,那么当其在执行park()的时候,因为拥有permit可以直接唤醒。
2.为什么unpark()两次后再park()两次,还是会被阻塞?
因为unpark()获取的permit至多只能拥有一个,调用两次unpark()也只会让被唤醒线程获得一个permit,而每次唤醒因park()阻塞的线程,需要消耗一个permit,因此在第二次park()的时候会被阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值