跳至主要內容

LockSupport与线程中断

Jin大约 18 分钟

LockSupport与线程中断

1、线程中断机制

返回值方法注释
voidinterrupt()中断此线程
static booleaninterrupted()测试当前线程是否已被中断
booleanisInterrupted()测试此线程是否已经被中断
  • 三个方法了解过吗?用在哪?
  • 如何停止一个运行中的线程?
  • 如何中断一个运行中的线程?

1.1、什么是中断机制?

  • 首先

    • 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
  • 其次

    • 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断

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

    • 若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;

    • 接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,

    • 此时究竟该做什么需要你自己写代码实现。

    • 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;

    • 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

  • eg.顾客在无烟餐厅中吸烟,服务员希望他别吸烟了,不是强行停止他吸烟,而是给他的标志位打为true,具体的停止吸烟还是要顾客自己停止。(体现了协商机制)

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

方法含义
public void interrupt()实例方法,实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
public static boolean interrupted()静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:1 返回当前线程的中断状态2 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。)
public boolean isInterrupted()实例方法,判断当前线程是否被中断(通过检查中断标志位)

1.3、大厂面试题:如何使用中断标识停止线程?

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

1.3.1.1、通过一个volatile变量实现
  • volatile保证了可见性,t2修改了标志位后能马上被t1看到

    public class VolatileDemo {
        static volatile boolean isStop = false;
    
        public static void main(String[] args) {
            new Thread(() -> {
                while (true) {
                    if (isStop) {//如果这个标志位被其他线程改为true了
                        System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序终止");
                        break;
                    }
                    System.out.println("t1 ------hello volatile");//----------------------如果没停止,那就一直打印
                }
            }, "t1").start();
    
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                isStop = true;
            }, "t2").start();
        }
    }
    
1.3.1.2、通过AtomicBoolean(原子布尔型)
public class AtomicBooleanDemo {
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

    public static void main(String[] args) {
        m1_volatile();
    }

    public static void m1_volatile() {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {//如果这个标志位被其他线程改为true了
                    System.out.println(Thread.currentThread().getName() + "\t atomicBoolean被修改为true,程序终止");
                    break;
                }
                System.out.println("t1 ------hello atomicBoolean");//----------------------如果没停止,那就一直打印
            }
        }, "t1").start();

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

        new Thread(() -> {
            atomicBoolean.set(true);
        }, "t2").start();
    }
}
1.3.1.3、 通过Thread类自带的中断api方法实现
public class InterruptedDemo {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {//一旦发现中断标志位被修改
                    System.out.println(Thread.currentThread().getName() + "\t isInterrupted()被修改为true,程序终止");
                    break;
                }
                System.out.println("t1 ------hello interrupt ");//----------------------如果没停止,那就一直打印
            }
        }, "t1");
        t1.start();

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

        new Thread(() -> {
            t1.interrupt();//把t1中断
        }, "t2").start();
        //        t1.interrupt();//也可以自己把自己(t1)中断
    }

}
Interrupte—API源码分析

实例方法interrupt(),没有返回值

    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
  • 进入interrupt0();
//Thread.java
    /* Some private helper methods */
    private native void setPriority0(int newPriority);
    private native void stop0(Object o);
    private native void suspend0();
    private native void resume0();
    private native void interrupt0();  //---------------------------调用了c底层
    private native void setNativeName(String name);
  • 实例方法isInterrupted,返回布尔值
    //Thread.java
	/**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);//也调用了c底层

说明

  • 具体来说,当对一个线程,调用 interrupt() 时:

    • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行

    • 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个InterruptedException异常。

    • (中断不活动的线程不会产生任何影响,看下面案例)

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

    • 仅仅设置了一个中断状态
  • 看看中断是否会立即停止这个300的线程
    • 否,虽然中断标志位变了。但是i一直在循环
//如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以, **interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行**。
public class InterruptDemo02 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println("---------" + i);
            }
            System.out.println("after t1.interrupt()---第2次----" + Thread.currentThread().isInterrupted());//true
        }, "t1");
        t1.start();
        System.out.println("before t1.interrupt()----" + t1.isInterrupted());//false
        t1.interrupt();
        try {
            TimeUnit.MILLISECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after t1.interrupt()---第1次---" + t1.isInterrupted());//true
        try {
            TimeUnit.MILLISECONDS.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after t1.interrupt()---第3次---" + t1.isInterrupted());//false,原因:此时t1已经运行结束。中断不活动的线程不会产生任何影响
    }
}
后手案例-深入
  • 在我们基本中断程序的骨架上 + 一个sleep阻塞
  • 中断异常 且 会导致程序无限循环.
//如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(中断状态将被清除),并抛出一个`InterruptedException`异常。
public class InterruptDemo03 {
    /**
     * 1 中断标志位 默认是false
     * <p>2 t2 ----->t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
     * <p>3 中断标志位true,正常情况下,程序停止,^-^
     * <p> 4 中断标志位true,异常情况下,InterruptedException,将会把中断状态清除,并且将收到InterruptedException。中断标志位false导致无限循环。
     * <p>
     * 5 在catch块中,需要再次给中断标志位设置为true,2次调用停止
     */

    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();
                    Thread.currentThread().interrupt();  //假如加了这个,程序可以终止,只会爆异常。若不加,sleep方法抛出InterruptedException后,中断标识也被清空置为false,我们在catch没有通过th.interrupt()方法再次将中断标志设置为true,这就导致无限循环了 
                }
                System.out.println("-----hello InterruptDemo03");
            }
        }, "t1");
        t1.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(t1::interrupt).start();
    }
}
image-20220710005520432
image-20220710005520432
  • // Thread.currentThread().interrupt(); 假如加了这个,程序可以终止,只会爆异常
为什么在异常处在调用异常interrupt()?
  • 原因:
    1. 中断标志位 默认是false
    2. t2 ----->t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
    3. 中断标志位true,正常情况下,程序停止,-
    4. 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态中断状态将被清除),并抛出一个InterruptedException异常。
    5. 中断标志位true,异常情况下,将会把中断状态清除,并且将收到InterruptedException。sleep方法抛出InterruptedException后,**中断标识也被清空置为false,**我们在catch没有通过interrupt()方法再次将中断标志设置为true,这就导致无限循环了 。
  • 所以在catch块中,需要再次给中断标志位设置为true,2次调用停止
  • 看下方interrupt方法官方 注释:then its interrupt status will be cleared and it will receive an
    /**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
小总结
  • 中断只是一种协同机制,修改中断标识位仅此而已,而不是立刻stop打断

1.3.3、静态方法Thread.interrupted()

  • api里的第二个

    • public static boolean interrupted()
  • 静态方法,Thread.interrupted();判断线程是否被中断,并清除当前中断状态这个方法做了两件事:

    1. 返回当前线程的中断状态
    2. 将当前线程的中断状态设为false(这个方法有点不好理解,因为连续调用两次的结果可能不一样。
public class InterruptedDemo {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main  false
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main  false
        System.out.println("-----1");//-----1
        Thread.currentThread().interrupt();//中断标志位设置为true
        System.out.println("-----2");//-----2
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main  true
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//main  false
    }
}

看下源码,interrupted()对比isInterrupted()

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

    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);
  • 他们在底层都调用了native方法isInterrupted

  • 只不过传入参数ClearInterrupted一个传参传了true,一个传了false。

    • 静态方法interrupted() 中true表示清空当前中断状态。

    • 实例方法isInterrupted 则不会。

2、LockSupport

  • 官方解释:用于创建锁和其他同步类的基本线程阻塞原语。

  • 核心就是park()和unpark()方法

    • park()方法是阻塞线程
    • unpark()方法是解除阻塞线程

文档参考:https://www.runoob.com/manual/jdk11api/java.base/java/util/concurrent/locks/LockSupport.html

变量和类型方法描述
static ObjectgetBlocker(Thread t)返回提供给尚未解除阻塞的park方法的最新调用的阻止程序对象,如果未阻止,则返回null。
static voidpark()除非许可证可用,否则禁用当前线程以进行线程调度。
static voidpark(Object blocker)除非许可证可用,否则禁用当前线程以进行线程调度。
static voidparkNanos(long nanos)除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的等待时间。
static voidparkNanos(Object blocker, long nanos)除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的等待时间。
static voidparkUntil(long deadline)除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的截止时间。
static voidparkUntil(Object blocker, long deadline)除非许可证可用,否则禁用当前线程以进行线程调度,直到指定的截止时间。
static voidunpark(Thread thread)如果给定线程尚不可用,则为其提供许可。

3、线程等待唤醒机制

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

  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程

  2. 使用JUC包中Conditionawait()方法让线程等待,使用signal()方法唤醒线程

  3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

    image-20220710125848925
    image-20220710125848925

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

  • 正常案例

        @Test
        public void demo1() {
            Object objectLock = new Object();
    
            new Thread(() -> {
                synchronized (objectLock) {
                    System.out.println(Thread.currentThread().getName() + "\t ---- come in");
                    try {
                        objectLock.wait();//----------------------这里先让他等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了");
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                synchronized (objectLock) {
                    objectLock.notify();//-------------------------再唤醒它
                    System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
                }
            }, "t2").start();
        }
    
    • 打印结果

      image-20220710225729930
      image-20220710225729930
  • 异常1—去掉synchronized

    • 说明要使用wait和notify必须加synchronized
        @Test
        public void demo2() {
            Object objectLock = new Object();
    
            new Thread(() -> {
                //    synchronized (objectLock) {
                System.out.println(Thread.currentThread().getName() + "\t ---- come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //      }
                System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了");
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(3L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                //     synchronized (objectLock) {
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
                //     }
            }, "t2").start();
    
        }
    
    • 打印结果

      image-20220710230759240
      image-20220710230759240
  • 异常2—把notify和wait的执行顺序对换

    • 说明顺序不能对换
        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() + "\t ---- come in");
                    try {
                        objectLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "---被唤醒了");
            }, "t1").start();
    
    
            new Thread(() -> {
                synchronized (objectLock) {
                    objectLock.notify();//这个先执行了
                    System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
                }
            }, "t2").start();
    
        }
    
    • 打印结果

      image-20220710230230791
      image-20220710230230791

小总结

  • wait和notify方法必须要在同步块或者方法里面,且成对出现使用
  • 先wait后notify才OK,顺序

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

  • 正常

       @Test
        public void demo1() {
            Lock lock = new ReentrantLock();
            Condition condition = 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();
                }
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                lock.lock();
                try {
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒");
            }, "t2").start();
        }
    
    • 打印结果

      image-20220710231015879
      image-20220710231015879
  • 异常1-不加锁

    @Test
        public void demo2() {
            Lock lock = new ReentrantLock();
            Condition condition = 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();
                }
            }, "t1").start();
    
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
    //            lock.lock();
                try {
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
    //                lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒");
            }, "t2").start();
        }
    
    • 打印结果:IllegalMonitorStateException
image-20220710231227918
image-20220710231227918
  • 异常2-condition.await();和 condition.signal()顺序互换

        public static void main(String[] args) {
            Lock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
    
            new Thread(() -> {
                //暂停几秒钟线程
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "\t-----come in");
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + "\t -----被唤醒");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }
            }, "t1").start();
    
    
            new Thread(() -> {
                lock.lock();
                try {
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "我要进行唤醒");
            }, "t2").start();
        }
    
    • 打印结果:

      image-20220710232211134
      image-20220710232211134

小总结

  • awaitsignal类似于上面waitnotify
    • Condition中的线程等待和唤醒方法,需要先获取锁
    • 一定要先await后signal,不能反了

3.4、Object和Condition使用的限制条件

  • 总结
    • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
    • 必须要先等待后唤醒,线程才能够被唤醒

3.5、LockSupport类中的park等待和unpark唤醒

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

  • 官网解释:https://www.runoob.com/manual/jdk11api/java.base/java/util/concurrent/locks/LockSupport.html

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

    • LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),

    • permit(许可)只有两个值1和0,默认是0。0 是阻塞,1是唤醒

    • 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。

许可(Permit)

3.5.1、阻塞

  • park()/park(Object blocker)

  • 调用LockSupport.park()时,发现它调用了unsafe类,并且默认传了一个0

    public static void park() {
            UNSAFE.park(false, 0L);
    }
    
  • permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为零并返回。

3.5.2、唤醒

  • 调用LockSupport.unpark();时,也调用了unsafe类

    public static void unpark(Thread thread) {
            if (thread != null)
                UNSAFE.unpark(thread);
        }
    
  • 调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

3.5.3、代码

  • 正常+无锁块要求

        @Test
        public void demo1() {
            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();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1");
            }, "t2").start();
        }
    
    • 打印结果

      image-20220710233052691
      image-20220710233052691
  • 之前错误的先唤醒后等待,LockSupport照样支持

        @Test
        public void demo2() {
            Thread t1 = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis());
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis());
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1");
            }, "t2").start();
    
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    
    • 打印结果

      image-20220710233337915
      image-20220710233337915
  • sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下。先执行了unpark(t1)导致上面的park方法形同虚设无效,时间是一样的 - 类似于高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。
  • 成双成对要牢记
  • 许可证是只要一个的

        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t----------come in" + "\t" + System.currentTimeMillis());
                LockSupport.park();
                LockSupport.park();//第二个阻塞
                System.out.println(Thread.currentThread().getName() + "\t----------被唤醒了" + "\t" + System.currentTimeMillis());
            }, "t1");
            t1.start();
    
            new Thread(() -> {
                LockSupport.unpark(t1);
                LockSupport.unpark(t1);
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName() + "\t-----发出通知,去唤醒t1");
            }, "t2").start();
    
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    
    • 打印结果

      image-20220710233930949
      image-20220710233930949

小总结

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

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

  • Lock Support提供park() 和unpark() 方法实现阻塞线程和解除线程阻塞的过程

  • Lock Support和每个使用它的线程都有一个许可(permit) 关联。

  • 每个线程都有一个相关的permit, permit最多只有一个, 重复调用un park也不会积累凭证。

形象的理解

  • 线程阻塞需要消耗凭证(permit) , 这个凭证最多只有1个。
  • 当调用方法时
    • 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
    • 如果无凭证,就必须阻塞等待凭证可用;
    • 而则相反, 它会增加一个凭证, 但凭证最多只能有1个, 累加无效。

面试题

  • 为什么可以突破wait/notify的原有调用顺序?

    • 因为un park获得了一个凭证, 之后再调用park方法, 就可以名正言顺的凭证消费, 故不会阻塞。先发放了凭证后续可以畅通无阻。
  • 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

    • 因为凭证的数量最多为1, 连续调用两次un park和调用一次un park效果一样, 只会增加一个凭证;而调用两次park却需要消费两个凭证, 证不够, 不能放行。
贡献者: Jin