LockSupport 是什么?怎么用?

前言

本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

本专栏目录结构和参考文献请见100个问题搞定Java并发

正文

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

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

关于 suspend 和 resume 请参考我的博客——线程的挂起(suspend)和继续执行(resume)是什么情况?

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

关于 wait 请参考我的博客——线程的等待(wait)和通知(notify)是什么情况?

LockSupport 的静态方法 park() 可以阻塞当前线程,类似的还有 parkNanos() 、 parkUntil() 等方法。

它们实现了一个限时的等待。

对比

为了更好的将 LockSupport 和 suspend 进行对比,这里我们举一个例子。

suspend

下面的代码中 suspend 方法将导致线程卡死。

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();
	}
}

LockSupport

现在用 LockSupport 重写这个程序:

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() 方法和 unpark() 方法做了替换。

当然,我们依然无法保证 unpark() 方法发生在 park() 方法之后。

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

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

它为每一个线程准备了一个许可,如果许可可用,那么 park() 方法会立即返回,并且消费这个许可(也就是将许可变为不可用)。

如果许可不可用,就会阻塞,而 unpack 方法则使得一个许可变为可用(但是和信号量不同的是,许可不能累加,你不可能拥有超过一个许可,它永远只有一个)。

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

这也就是上述代码可顺利结束的主要原因。

线程状态 WAITING (parking)

同时,处于 parko 方法挂起状态的线程不会像 suspend 方法那样还给出一个令人费解的 Runnable 状态。

它会非常明确地给出一个 WAITING 状态,甚至还会标注是 park() 方法引起的。

这使得分析问题时格外方便。

"t1" #8 prio=5 os_prio=0 tid=0x00b1a400 nid=0x1994 waiting on condition [0x1619f000]
    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.shockang.study.java.concurrent.lock.LockSupportDemo$ChangeObjectThread.run(LockSupportDemo.java: 19) 
        - locked <0x048b2680> (a java.lang.Object)

此外,如果你使用 park(Object)函数,那么还可以为当前线程设置一个阻塞对象。

这个阻塞对象会出现在线程 Dump 中。

这样在分析问题时,就更加方便了。

比如,我们将上述代码第 14 行的 park() 方法改为

LockSupport.park(this);

那么在线程 Dump 时,你可能会看到如下信息:

"t1" #8 prio=5 os_prio=0 tid=0x0117ac00 nid=0x2034 waiting on condition [0x15d0f000]
    java.lang.Thread.State: WAITING (parking) 
        at sun.misc.Unsafe.park(Native Method) 
        - parking to wait for <0x048b4738> (a com.shockang.study.java.concurrent.lock.LockSupportDemo$ChangeObjectThrea)
        at java.util..concurrent.locks..LockSupport.park(LockSupport.java: 304) 
        at com.shockang.study.java.concurrent.lock.LockSupportDemo$ChangeObjectThread.run(LockSupportDemo.java: 19) 
        - locked <0x048b2808> (a java.lang.Object)

注意,在堆栈中,我们甚至还看到了当前线程等待的对象,这里就是 ChangeObjectThread 实例

支持中断

除了有定时阻塞的功能, 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);
    }
}

控制台

in t1
t1 被中断了
t1执行结束
in t2
t2执行结束
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
ConcurrentHashMap 是 Java 中的一个线程安全的哈希映射实现,它是在 HashMap 基础上进行了并发性的优化。它的主要实现原理包括: 1. 分段锁(Segmented Locking):将整个哈希表划分为多个独立的段(Segment),每个段有自己的锁。这样,在对特定键值操作时,只需要锁定对应的那段数据,而不是整个 Map,大大提高了并发性能。 2. 并行化查找和插入:当多个线程同时访问时,它们可以在不同段中并行进行查找或插入操作。ConcurrentHashMap 提供了 `putIfAbsent`、`replace()` 等方法的无锁版本(CAS),用于原子操作。 3. 冗余探测(Redundant Reads Detection):对于读取操作,如果一个线程看到的数据后来被其他线程更新了,ConcurrentHashMap 会检测到这种“冗余读”,并且能够正确地返回最新的值。 4. 锁升级与降级策略:为了进一步提高性能,当多个线程竞争同一段的锁时,可能会经历从轻量级锁(LockSupport.parkNanos 或 park/unpark)到重量级锁再到无锁 CAS 的过程,这被称为锁的升级与降级。 5. 非阻塞删除:删除操作相对复杂,因为它涉及到迁移元素。ConcurrentHashMap 使用了一种称为 "Split-Brain Operation"(SBO)的方法,即预计算删除后的影响,然后异步完成实际删除。 相关问题-- 1. ConcurrentHashMap 如何处理多线程竞争? 2. 在高并发情况下,ConcurrentHashMap 如何避免死锁? 3. 删除操作在 ConcurrentHashMap 中是如何实现的?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值