LockSupport 是什么?怎么用?

502 阅读3分钟

LockSupport 是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。

与 Thread.suspend() 方法相比,它弥补了由于 resume() 方法发生导致线程无法继续执行的情况。

和 Object.wait() 方法相比,它不需要先获得某个对象的锁,也不会抛出 InterruptedException 异常

LockSupport 的静态方法 park() 可以阻塞当前线程,类似的还有 parkNanos()、 parkUntil() 等方法。它们实现了一个限时的等待。

支持定时阻塞

一个错误的例子

package com.shockang.study.java.concurrent.thread.suspend;

public class SuspendDemo {
	public static Object u = new Object();
	static ChangeObjectThread t1 = new ChangeObjectThread("t1");
	static ChangeObjectThread t2 = new ChangeObjectThread("t2");

	public static class ChangeObjectThread extends Thread {
		public ChangeObjectThread(String name){
			super.setName(name);
		}
		@Override
		public void run() {
			synchronized (u) {
				System.out.println("in "+getName());
				Thread.currentThread().suspend();
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		t1.start();
		Thread.sleep(100);
		t2.start();
		t1.resume();
		t2.resume();
		t1.join();
		t2.join();
	}
}

上面程序的执行结果是: 在这里插入图片描述 在当前系统中,线程 2 其实是被挂起的,但是它的线程状态确实是 RUNNABLE ,这很有可能使我们误判当前系统的状态。 同时,虽然主函数中已经调用了 resume() 方法,但是由于时间先后顺序的缘故,那个 resume 并没有生效! 这就导致了线程 2 被永远挂起,并且永远占用了对象 u 的锁。 这对于系统来说极有可能是致命的。

怎么改进?

package com.shockang.study.java.concurrent.lock;

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
    private static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

在这里插入图片描述

注意,这里只是将原来的 suspend方法和 resume方法用 park() 方法和 unpack() 方法做了替换。

我们无法保证 unpack() 方法发生在 park() 方法之后。

但是执行这段代码,你会发现,它自始至终都可以正常地结東,不会因为 park() 方法而导致线程水久挂起。

这是因为 LockSupport 类使用类似信号量的机制。

它为每一个线程准备了一个许可,如果许可可用,那么 park() 方法会立即返回,并且消费这个许可(也就是将许可变为不可用),如果许可不可用,就会阻塞,而 unpark() 方法则使得一个许可变为可用(但是和信号量不同的是,许可不能累加,你不可能拥有超过一个许可,它永远只有一个)。

这个特点使得:即使 unpark() 方法操作发生在 park() 方法之前,它也可以使下一次的 park() 方法操作立即返回。

支持中断影响

除了有定时阻塞的功能, LockSupport.park() 方法还能支持中断影响。

但是和其他接收中断的函数很不一样, LockSupport.park() 方法不会抛出 InterruptedException 异常。

它只会默默返回,但是我们可以从 Thread.interrupted() 等方法中获得中断标记。

package com.shockang.study.java.concurrent.lock;

import java.util.concurrent.locks.LockSupport;

public class LockSupportIntDemo {
    private static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
                if (Thread.interrupted()) {
                    System.out.println(getName() + " 被中断了");
                }
            }
            System.out.println(getName() + "执行结束");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.interrupt();
        LockSupport.unpark(t2);
    }
}

在这里插入图片描述