[JUC] LockSupport浅析

目录

  • wait/notify
  • LockSupport
  • wait/notify和LockSupport对比
  • LockSupport注意事项
  • 参考文献

LockSupport是Java6引入的一个工具类,它简单灵活,应用广泛。在没有LockSupport之前,线程的挂起和唤醒咱们都是通过Object的wait和notify/notifyAll方法实现。

我们以例子来说明两者之间的区别

wait/notify

线程t执行一段业务逻辑后调用wait阻塞住自己。主线程调用notify方法唤醒线程A,线程A然后打印自己执行的结果:

public class WaitTest {
    public static void main(String[] args) throws Exception {
        final Object obj = new Object();
        Thread t = new Thread(() -> {
            int sum = IntStream.range(0, 11).sum();
            try {
                obj.wait();
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(sum);
        });
        t.start();
        //睡眠一秒钟,保证线程t已经计算完成,阻塞在wait方法
        Thread.sleep(1000);
        obj.notify();
    }
}

执行这段代码,不难发现这个错误:

java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.wait(Native Method)
	at java.base/java.lang.Object.wait(Object.java:328)
	at com.ctrip.flight.test.concurrent.WaitTest.lambda$main$0(WaitTest.java:16)
	at java.base/java.lang.Thread.run(Thread.java:834)
55
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.base/java.lang.Object.notify(Native Method)
	at com.ctrip.flight.test.concurrent.WaitTest.main(WaitTest.java:25)

原因很简单,wait和notify/notifyAll方法只能在同步代码块里用。所以将代码修改为如下就可正常运行了:

public class WaitTest {
    public static void main(String[] args) throws Exception {
        final Object obj = new Object();
        Thread t = new Thread(() -> {
            int sum = IntStream.range(0, 11).sum();
            try {
                synchronized (obj) {
                    obj.wait();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(sum);
        });
        t.start();
        //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
        Thread.sleep(1000);
        synchronized (obj) {
            obj.notify();
        }
    }
}

上述示例中我们在main线程sleep了1秒钟等待t线程执行到wait操作为止,试想如果main线程不sleep,并且先于t线程执行notify方法会发生出什么情况:

public class WaitTest {
    public static void main(String[] args) throws Exception {
        final Object obj = new Object();
        Thread t = new Thread(() -> {
            int sum = IntStream.range(0, 11).sum();
            try {
                synchronized (obj) {
                    obj.wait();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(sum);
        });
        t.start();
        // 睡眠一秒钟,保证线程t已经计算完成,阻塞在wait方法
        // Thread.sleep(1000); 不等待t线程执行到wait方法
        synchronized (obj) {
            obj.notify();
        }
    }
}

多执行几次会发现JVM进程无法退出,是因为mian线程在执行完notify后退出了,而此时t线程可能刚好执行到wait,再也没有接收到notify了,t线程就一直处于等待状态,因为t不是daemon线程,JVM进程不会退出(只有当当前运行的所有线程都是deamon线程时JVM才会退出)。t线程要先使用synchronized获取obj对象的锁,使得main线程无法执行notify,阻塞在synchronized (obj)操作,接着t线程执行wait操作释放obj的锁,并将自身阻塞起来,main线程重新获得obj的锁,执行notify方法,退出synchronized代码块释放obj锁后t线程在obj.wait()处重新获取锁,继续往下执行。

LockSupport

下面使用LockSupport实现同样的功能:

public class LockSupportTest {
    public static void main(String[] args) throws Exception {
        Thread t = new Thread(() -> {
            int sum = IntStream.range(0, 11).sum();
            LockSupport.park();
            System.out.println(sum);
        });
        t.start();
        LockSupport.unpark(t);
    }
}

这段代码无论如何执行,JVM进程都可以正常退出,即使main线程的LockSupport.unpark(t);先于t线程的LockSupport.park();执行。

wait/notify和LockSupport对比

通过对比可以发现LockSupport相比wait/notify具有两大优势:

  • LockSupport不需要同步代码块,而wait/notify需要使用synchronized来同步代码块,同步的两个线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦;
  • unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序,而wait必须要先于notify调用;

LockSupport注意事项

  • 多次调用·unpark方法和调用一次unpark方法效果一样,例如线程A连续调用两次LockSupport.unpark(B)方法唤醒线程B,然后线程B调用两次LockSupport.park()方法, 线程B依旧会被阻塞;

参考文献

自己动手写把”锁”—LockSupport深入浅出

你可能感兴趣的:(java,并发)