java高并发系列 - 第14天:JUC中的LockSupport工具类,必备技能

这是java高并发系列第14篇文章。java

本文主要内容:微信

  1. 讲解3种让线程等待和唤醒的方法,每种方法配合具体的示例
  2. 介绍LockSupport主要用法
  3. 对比3种方式,了解他们之间的区别

LockSupport位于java.util.concurrent简称juc)包中,算是juc中一个基础类,juc中不少地方都会使用LockSupport,很是重要,但愿你们必定要掌握。并发

关于线程等待/唤醒的方法,前面的文章中咱们已经讲过2种了:ide

  1. 方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  2. 方式2:使用juc包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

这2种方式,咱们先来看一下示例。高并发

使用Object类中的方法实现线程等待和唤醒

示例1:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo1 {

    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        synchronized (lock) {
            lock.notify();
        }
    }
}

输出:工具

1563592938744,t1 start!
1563592943745,t1 被唤醒!

t1线程中调用lock.wait()方法让t1线程等待,主线程中休眠5秒以后,调用lock.notify()方法唤醒了t1线程,输出的结果中,两行结果相差5秒左右,程序正常退出。线程

示例2

咱们把上面代码中main方法内部改一下,删除了synchronized关键字,看看有什么效果:code

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo2 {

    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        lock.notify();
    }
}

运行结果:对象

Exception in thread "t1" java.lang.IllegalMonitorStateException
1563593178811,t1 start!
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.itsoku.chat10.Demo2.lambda$main$0(Demo2.java:16)
    at java.lang.Thread.run(Thread.java:745)
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at com.itsoku.chat10.Demo2.main(Demo2.java:26)

上面代码中将synchronized去掉了,发现调用wait()方法和调用notify()方法都抛出了IllegalMonitorStateException异常,缘由:Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在同步代码中运行(必须用到关键字synchronized)token

示例3

唤醒方法在等待方法以前执行,线程可以被唤醒么?代码以下:

package com.itsoku.chat10;

import java.util.concurrent.TimeUnit;

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo3 {

    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock) {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    //休眠3秒
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠1秒以后唤醒lock对象上等待的线程
        TimeUnit.SECONDS.sleep(1);
        synchronized (lock) {
            lock.notify();
        }
        System.out.println("lock.notify()执行完毕");
    }
}

运行代码,输出结果:

lock.notify()执行完毕
1563593869797,t1 start!

输出了上面2行以后,程序一直没法结束,t1线程调用wait()方法以后没法被唤醒了,从输出中可见,notify()方法在wait()方法以前执行了,等待的线程没法被唤醒了。说明:唤醒方法在等待方法以前执行,线程没法被唤醒。

关于Object类中的用户线程等待和唤醒的方法,总结一下:

  1. wait()/notify()/notifyAll()方法都必须放在同步代码(必须在synchronized内部执行)中执行,须要先获取锁
  2. 线程唤醒的方法(notify、notifyAll)须要在等待的方法(wait)以后执行,等待中的线程才可能会被唤醒,不然没法唤醒

使用Condition实现线程的等待和唤醒

Condition的使用,前面的文章讲过,对这块不熟悉的能够移步JUC中Condition的使用,关于Condition咱们准备了3个示例。

示例1

package com.itsoku.chat10;

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

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo4 {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
            } finally {
                lock.unlock();
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }

    }
}

输出:

1563594349632,t1 start!
1563594354634,t1 被唤醒!

t1线程启动以后调用condition.await()方法将线程处于等待中,主线程休眠5秒以后调用condition.signal()方法将t1线程唤醒成功,输出结果中2个时间戳相差5秒。

示例2

咱们将上面代码中的lock.lock()、lock.unlock()去掉,看看会发生什么。代码:

package com.itsoku.chat10;

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

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo5 {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        condition.signal();
    }
}

输出:

Exception in thread "t1" java.lang.IllegalMonitorStateException
1563594654865,t1 start!
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
    at com.itsoku.chat10.Demo5.lambda$main$0(Demo5.java:19)
    at java.lang.Thread.run(Thread.java:745)
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
    at com.itsoku.chat10.Demo5.main(Demo5.java:29)

有异常发生,condition.await();condition.signal();都触发了IllegalMonitorStateException异常。缘由:调用condition中线程等待和唤醒的方法的前提是必需要先获取lock的锁

示例3

唤醒代码在等待以前执行,线程可以被唤醒么?代码以下:

package com.itsoku.chat10;

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

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo6 {

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
            try {
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
            } finally {
                lock.unlock();
            }
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(1);
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
        System.out.println(System.currentTimeMillis() + ",condition.signal();执行完毕");
    }
}

运行结果:

1563594886532,condition.signal();执行完毕
1563594890532,t1 start!

输出上面2行以后,程序没法结束,代码结合输出能够看出signal()方法在await()方法以前执行的,最终t1线程没法被唤醒,致使程序没法结束。

关于Condition中方法使用总结:

  1. 使用Condtion中的线程等待和唤醒方法以前,须要先获取锁。否者会报IllegalMonitorStateException异常
  2. signal()方法先于await()方法以前调用,线程没法被唤醒

Object和Condition的局限性

关于Object和Condtion中线程等待和唤醒的局限性,有如下几点:

  1. 2中方式中的让线程等待和唤醒的方法可以执行的先决条件是:线程须要先获取锁
  2. 唤醒方法须要在等待方法以后调用,线程才可以被唤醒

关于这2点,LockSupport都不须要,就能实现线程的等待和唤醒。下面咱们来讲一下LockSupport类。

LockSupport类介绍

LockSupport类能够阻塞当前线程以及唤醒指定被阻塞的线程。主要是经过park()unpark(thread)方法来实现阻塞和唤醒线程的操做的。

每一个线程都有一个许可(permit),permit只有两个值1和0,默认是0。

  1. 当调用unpark(thread)方法,就会将thread线程的许可permit设置成1(注意屡次调用unpark方法,不会累加,permit值仍是1)。
  2. 当调用park()方法,若是当前线程的permit是1,那么将permit设置为0,并当即返回。若是当前线程的permit是0,那么当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,而后会将permit再次设置为0,并返回。

注意:由于permit默认是0,因此一开始调用park()方法,线程一定会被阻塞。调用unpark(thread)方法后,会自动唤醒thread线程,即park方法当即返回。

LockSupport中经常使用的方法

阻塞线程

  • void park():阻塞当前线程,若是调用unpark方法或者当前线程被中断,从能从park()方法中返回

  • void park(Object blocker):功能同方法1,入参增长一个Object对象,用来记录致使线程阻塞的阻塞对象,方便进行问题排查

  • void parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,增长了超时返回的特性

  • void parkNanos(Object blocker, long nanos):功能同方法3,入参增长一个Object对象,用来记录致使线程阻塞的阻塞对象,方便进行问题排查

  • void parkUntil(long deadline):阻塞当前线程,直到deadline,deadline是一个绝对时间,表示某个时间的毫秒格式

  • void parkUntil(Object blocker, long deadline):功能同方法5,入参增长一个Object对象,用来记录致使线程阻塞的阻塞对象,方便进行问题排查;

唤醒线程

  • void unpark(Thread thread):唤醒处于阻塞状态的指定线程

示例1

主线程线程等待5秒以后,唤醒t1线程,代码以下:

package com.itsoku.chat10;

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

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo7 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            LockSupport.park();
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        LockSupport.unpark(t1);
        System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();执行完毕");
    }
}

输出:

1563597664321,t1 start!
1563597669323,LockSupport.unpark();执行完毕
1563597669323,t1 被唤醒!

t1中调用LockSupport.park();让当前线程t1等待,主线程休眠了5秒以后,调用LockSupport.unpark(t1);将t1线程唤醒,输出结果中一、3行结果相差5秒左右,说明t1线程等待5秒以后,被唤醒了。

LockSupport.park();无参数,内部直接会让当前线程处于等待中;unpark方法传递了一个线程对象做为参数,表示将对应的线程唤醒。

示例2

唤醒方法放在等待方法以前执行,看一下线程是否可以被唤醒呢?代码以下:

package com.itsoku.chat10;

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

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo8 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            LockSupport.park();
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠1秒
        TimeUnit.SECONDS.sleep(1);
        LockSupport.unpark(t1);
        System.out.println(System.currentTimeMillis() + ",LockSupport.unpark();执行完毕");
    }
}

输出:

1563597994295,LockSupport.unpark();执行完毕
1563597998296,t1 start!
1563597998296,t1 被唤醒!

代码中启动t1线程,t1线程内部休眠了5秒,而后主线程休眠1秒以后,调用了LockSupport.unpark(t1);唤醒线程t1,此时LockSupport.park();方法还未执行,说明唤醒方法在等待方法以前执行的;输出结果中二、3行结果时间同样,表示LockSupport.park();没有阻塞了,是当即返回的。

说明:唤醒方法在等待方法以前执行,线程也可以被唤醒,这点是另外2中方法没法作到的。Object和Condition中的唤醒必须在等待以后调用,线程才能被唤醒。而LockSupport中,唤醒的方法不论是在等待以前仍是在等待以后调用,线程都可以被唤醒。

示例3

park()让线程等待以后,是否可以响应线程中断?代码以下:

package com.itsoku.chat10;

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

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo9 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " start!");
            System.out.println(Thread.currentThread().getName() + ",park()以前中断标志:" + Thread.currentThread().isInterrupted());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + ",park()以后中断标志:" + Thread.currentThread().isInterrupted());
            System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + " 被唤醒!");
        });
        t1.setName("t1");
        t1.start();
        //休眠5秒
        TimeUnit.SECONDS.sleep(5);
        t1.interrupt();

    }
}

输出:

1563598536736,t1 start!
t1,park()以前中断标志:false
t1,park()以后中断标志:true
1563598541736,t1 被唤醒!

t1线程中调用了park()方法让线程等待,主线程休眠了5秒以后,调用t1.interrupt();给线程t1发送中断信号,而后线程t1从等待中被唤醒了,输出结果中的一、4行结果相差5秒左右,恰好是主线程休眠了5秒以后将t1唤醒了。结论:park方法能够相应线程中断。

LockSupport.park方法让线程等待以后,唤醒方式有2种:

  1. 调用LockSupport.unpark方法
  2. 调用等待线程的interrupt()方法,给等待的线程发送中断信号,能够唤醒线程

示例4

LockSupport有几个阻塞放有一个blocker参数,这个参数什么意思,上一个实例代码,你们一看就懂了:

package com.itsoku.chat10;

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

/**
 * 微信公众号:javacode2018,获取年薪50万课程
 */
public class Demo10 {

    static class BlockerDemo {
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
        });
        t1.setName("t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            LockSupport.park(new BlockerDemo());
        });
        t2.setName("t2");
        t2.start();
    }
}

运行上面代码,而后用jstack查看一下线程的堆栈信息:

"t2" #13 prio=5 os_prio=0 tid=0x00000000293ea800 nid=0x91e0 waiting on condition [0x0000000029c3f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000007180bfeb0> (a com.itsoku.chat10.Demo10$BlockerDemo)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at com.itsoku.chat10.Demo10.lambda$main$1(Demo10.java:22)
        at com.itsoku.chat10.Demo10$$Lambda$2/824909230.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

"t1" #12 prio=5 os_prio=0 tid=0x00000000293ea000 nid=0x9d4 waiting on condition [0x0000000029b3f000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
        at com.itsoku.chat10.Demo10.lambda$main$0(Demo10.java:16)
        at com.itsoku.chat10.Demo10$$Lambda$1/1389133897.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

代码中,线程t1和t2的不一样点是,t2中调用park方法传入了一个BlockerDemo对象,从上面的线程堆栈信息中,发现t2线程的堆栈信息中多了一行- parking to wait for &lt;0x00000007180bfeb0&gt; (a com.itsoku.chat10.Demo10$BlockerDemo),恰好是传入的BlockerDemo对象,park传入的这个参数可让咱们在线程堆栈信息中方便排查问题,其余暂无他用。

LockSupport的其余等待方法,包含有超时时间了,过了超时时间,等待方法会自动返回,让线程继续运行,这些方法在此就不提供示例了,有兴趣的朋友能够本身动动手,练一练。

线程等待和唤醒的3种方式作个对比

到目前为止,已经说了3种让线程等待和唤醒的方法了

  1. 方式1:Object中的wait、notify、notifyAll方法
  2. 方式2:juc中Condition接口提供的await、signal、signalAll方法
  3. 方式3:juc中的LockSupport提供的park、unpark方法

3种方式对比:

Object Condtion LockSupport
前置条件 须要在synchronized中运行 须要先获取Lock的锁
无限等待 支持 支持 支持
超时等待 支持 支持 支持
等待到未来某个时间返回 不支持 支持 支持
等待状态中释放锁 会释放 会释放 不会释放
唤醒方法先于等待方法执行,可否唤醒线程 能够
是否能响应线程中断
线程中断是否会清除中断标志
是否支持等待状态中不响应中断 不支持 支持 不支持

java高并发系列连载中,总计估计会有四五十篇文章,能够关注公众号:javacode2018,获取最新文章。

java高并发系列 - 第14天:JUC中的LockSupport工具类,必备技能

相关文章
相关标签/搜索