`
春花秋月何时了
  • 浏览: 41844 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Java并发包基础元件LockSupport

 
阅读更多

前言

LockSupport 和 CAS 是Java并发包中很多并发工具控制机制(Lock和同步器框架的核心 AQS: AbstractQueuedSynchronizer)的基础,它们底层其实都是依赖Unsafe实现。

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。其主要的核心就是提供了park()和unpark()方法来实现阻塞线程和解除线程阻塞。

其基本原理类似于二元信号量(只有1个许可证"permit"可供使用),当执行park()的时候,如果这个唯一许可证还没有被占用,当前线程则获取该唯一许可继续往下执行,如果许可已经被占用,则当前线程阻塞,等待获取许可。当执行unpark()的时候,将释放对应线程的许可。

 

park/unpark方法详解

首先看LockSupport 中相关方法的源码(此处只列举了最基本的park方法,其他带超时时间参数的park方法就不再一一介绍)

public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
}

public static void park() {
        UNSAFE.park(false, 0L);
}

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
}

通过源码可以发现,park/unpark的底层都是通过调用unsafe类的相关实现,对于park的其他增加了超时时间的变种park方法也是根据unsafe的park方法的第一个参数是否是绝对时间来扩展的。

关于park/unpark方法在openJDK里的C++实现,主要是利用了Posix的mutex,condition来实现的,至于什么是Posix、什么是mutex,condition,就不在深入探索了,只要知道这种实现是和平台紧密相关的,大概就是借助了操作系统的某些实现。总之其内部维护了一个volatile修饰的int类型的_counter变量来记录所谓的“许可”。当park时,这个变量置为了0,当unpark时,这个变量置为1。

 

值得注意的是,由于park的底层调用的是unsafe的park实现,所以当调用LockSupport的park()方法时如果没有立即获得许可,那么当前线程阻塞之后,也只有出现如下几种情况才会退出阻塞状态,立即返回:

1)其他线程执行了当前线程的unpark()方法2)其他线程打断了当前线程3)如果park方法带的超时时间不为0,当超时时间到达时4)无理由的虚假的唤醒(也就是传说中“Spurious wakeup”,和Object类的wait()方法类似)。

前三种情况都很好理解,第四种情况似乎有点让人无法接受,为什么会存在无缘无故的就被唤醒的情况?这样如何保证我们的应用不出现错误?Google了很多关于Spurious wakeup的文章,大概有如下几种解释:

第一种解释:通过分析源码发现底层的pthread_cond_wait方法并不是放在一个while循环中,而是if判断中,这样当pthread_cond_wait被唤醒之后,并不会再次进行条件判断,而是立即返回至上层应用。我认为这其实并不能称之为一种解释,最多算一种最肤浅最表面的原因,更重要的应该是为何pthread_cond_wait方法会在条件不成立的情况下返回。

第二种解释:这种解释认为这是出于性能考虑的原因“Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations”。但这看起来也像是一种模棱两可的解释。

第三种解释:认为这是操作系统本身的一种策略“Each blocking system call on Linux returns abruptly with EINTR when the process receives a signal. ... pthread_cond_wait() can't restart the waiting because it may miss a real wakeup in the little time it was outside the futex system call.”

总而言之,这种无理由的虚假的唤醒是存在的,但是几率应该是比较少的,到底的出于什么原因导致的,我们可以不用深究。针对这种情况,如何保证我们的应用不受这种虚假唤醒的影响,网络上的答案到是一致的,那就是:将park()方法的调用置于循环检查是否满足条件的代码块中:

while (<condition does not hold>)
     LockSupport.park();
    ... //执行适合条件的动作 

 

park/unpark特性详解

1. 许可默认是被占用的,也就是说如果在没有先执行unpark的情况下,直接执行park()将获取不到许可,从而被阻塞。示例如下:

public static void main(String[] args)
{
     LockSupport.park();//许可默认已经被占用,此处将阻塞
     System.out.println("block.");//这里将不会得到执行
}

2. LockSupport不可重入,但unpark可以多次调用。 

public static void main(String[] args)
{
     Thread thread = Thread.currentThread();
     LockSupport.unpark(thread);//释放许可
	 System.out.println("a");
	 LockSupport.unpark(thread);//再次释放许可,也是可以的。
	 System.out.println("b");
     LockSupport.park();// 获取许可
     System.out.println("c");
	 LockSupport.park();//不可重入,导致阻塞
     System.out.println("d");
}
 以上代码中只会打印出:a,b,c。不会打印出c。因为第二次调用park的时候,线程无法获取许可从而导致阻塞。

 

3. 支持被中断

public static void main(String[] args) throws Exception {
		Thread t = new Thread(new Runnable() {
			private int count = 0;

			@Override
			public void run() {
				long start = System.currentTimeMillis();
				long end = 0;

				while ((end - start) <= 1000) {
					count++;
					end = System.currentTimeMillis();
				}

				System.out.println("before park.count=" + count);

				LockSupport.park();//被阻塞
				System.out.println("thread over." + Thread.currentThread().isInterrupted());

			}
		});

		t.start();

		Thread.sleep(5000);

		t.interrupt();

		System.out.println("main over");
}
当线程t执行 LockSupport.park()的时候,由于许可默认被占用,所以被阻塞,但是主线程在5秒只后对t线程进行了打断,导致LockSupport.park()被唤醒,打印出thread over.true。由此可见线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException 。 注意:如果在执行park之前就执行了中断操作将线程打断,在接下来调用park时将会立即返回,并且不会清除中断状态。

4. 唤醒信号不担心丢失

在wait/notify/notifyAll模式的阻塞/唤醒机制中,我们必须要考虑 notify和wait调用的时序性,避免在wait方法调用之前调用了notify,从而导致错过唤醒信号,使应用永远等待。而LockSupport的unpark()方法可以在park()方法调用之前、之后甚至同时执行,都可以达到唤醒线程的目的。并且park和Object.wait()本质实现机制不同,两者的阻塞队列并不交叉,object.notifyAll()不能唤醒LockSupport.park()阻塞的线程。

5. 方便线程监控与工具定位 

在LockSupport类中存在parkBlocker的getter、setter方法, 可以看到它是通过unsafe运用Thread类的实例成员属性parkBlocker的偏移地址获取对应线程的parkBlocker成员属性的值。

private static final long parkBlockerOffset;

static{
try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
        } catch (Exception ex) { throw new Error(ex); }

}
public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}

private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
}

 这个parkBlocker对象是用来记录线程被阻塞时被谁阻塞的。可以通过LockSupport的getBlocker获取到阻塞的对象.用于线程监控和分析工具来定位原因的。

 

nextSecondarySeed方法

static final int nextSecondarySeed() {
	int r;
	Thread t = Thread.currentThread();
	if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
		r ^= r << 13;   // xorshift
		r ^= r >>> 17;
		r ^= r << 5;
	}
	else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
		r = 1; // avoid zero
	UNSAFE.putInt(t, SECONDARY, r);
	return r;
}


    LockSupport类中还提供了上面这个叫nextSecondarySeed的方法,它其实操作的是Thread类中的一些关于伪随机数的成员属性:threadLocalRandomSeed、threadLocalRandomProbe、threadLocalRandomSecondarySeed
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
	try {
		UNSAFE = sun.misc.Unsafe.getUnsafe();
		Class<?> tk = Thread.class;
		SEED = UNSAFE.objectFieldOffset
			(tk.getDeclaredField("threadLocalRandomSeed"));
		PROBE = UNSAFE.objectFieldOffset
			(tk.getDeclaredField("threadLocalRandomProbe"));
		SECONDARY = UNSAFE.objectFieldOffset
			(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
	} catch (Exception ex) { throw new Error(ex); }
}

   从nextSecondarySeed方法的实现,可以看出它主要是为了生成随机数,并且把随机数保存到当前线程对象的成员属性作为下一次生成随机数的因子,在其中还用到了并发包特别提供的随机数生成工具ThreadLocalRandom,其实在 ThreadLocalRandom类中也提供了一个几乎一模一样的nextSecondarySeed方法,为什么不使用Random而要重新创建一个ThreadLocalRandom?这主要是为了解决Random类在多线程下多个线程竞争内部唯一的原子性种子变量而导致大量线程自旋重试的问题,至于其深层次的内部原理,会在专门的ThreadLocalRandom分析文章中进行分析介绍。

 
分享到:
评论

相关推荐

    Java并发编程之LockSupport、Unsafe详解.docx

    在并发编程中,理解并适当使用LockSupport和Unsafe可以帮助我们创建更高效、可控的多线程程序。例如,自定义的锁实现可能会利用LockSupport的`park`和`unpark`来实现线程的阻塞和唤醒,而Unsafe则可以用于优化内存...

    详解Java多线程编程中LockSupport类的线程阻塞用法

    LockSupport并不像synchronized或java.util.concurrent.locks包中的Lock接口那样提供锁的完整功能,但它提供了两个核心方法:park()和unpark(),用于线程的阻塞和解除阻塞,解决了传统Thread.suspend和Thread.resume...

    Java中LockSupport的使用.docx

    总之,LockSupport是Java并发编程中一个强大的工具,它提供了低级别的线程阻塞和唤醒操作,是构建高级同步工具的基础。通过理解和合理使用`park()`和`unpark()`,开发者可以创建出更高效、可控的多线程应用程序。...

    Java并发包源码分析(JDK1.8)

    Java并发包源码分析(JDK1.8):囊括了java.util.concurrent包中大部分类的源码分析,其中涉及automic包,locks包(AbstractQueuedSynchronizer、ReentrantLock、ReentrantReadWriteLock、LockSupport等),queue...

    java线程阻塞中断与LockSupport使用介绍

    `Thread.interrupt()` 方法是用于向线程发送中断请求,而`LockSupport` 是Java 5引入的一个低级别的线程同步工具,提供了比`synchronized` 和 `java.util.concurrent.locks` 包更细粒度的控制。下面我们将详细讨论这...

    LockSupport

    LockSupport.xmid总结,用于知识巩固,

    Java 多线程与并发(9-26)-JUC锁- LockSupport详解.pdf

    LockSupport是Java中用于多线程同步的一个工具类,它提供了一组基础的线程阻塞和解除阻塞的方法。这个类位于java.util.concurrent.locks包下,是实现并发编程中AQS(AbstractQueuedSynchronizer)框架的重要基础之一...

    Java concurrency之LockSupport_动力节点Java学院整理

    Java concurrency之LockSupport是Java并发编程中的一种基础设施,用于创建锁和其他同步类的基本线程阻塞原语。LockSupport提供了一组函数来实现线程之间的同步和通信,使得开发者可以轻松地创建高效、可靠的并发程序...

    Java并发编程学习之Unsafe类与LockSupport类源码详析

    2. LockSupport是许多高级并发工具如Semaphore、CountDownLatch和CyclicBarrier的基础,通过它可以实现自定义的同步原语。 三、Unsafe与LockSupport的应用场景 1. 实现高效并发容器:如ConcurrentHashMap、...

    基于JDK源码解析Java领域中的并发锁之设计与实现.pdf

    通过以上分析,我们可以看到Java并发包提供了丰富的并发锁机制,从简单的synchronized关键字到复杂的AQS、LockSupport、Condition等,这些都是为了解决并发编程中的互斥和同步问题。在实际应用中,开发者需要根据...

    JAVA面试题2019

    以上是对“JAVA面试题2019”中提及的一些核心知识点的总结,涵盖了项目介绍、Java基础知识、并发编程、Spring框架、Netty框架以及分布式系统等方面的知识点。这些知识点不仅对于准备Java面试至关重要,同时也是Java...

    Java线程状态流转图

    Java线程状态流转图知识点总结 Java线程状态流转图是一种用于描述Java线程生命周期中不同的状态和状态...通过了解Java线程状态流转图,我们可以更好地理解Java线程的生命周期,并提高Java多线程编程的效率和可靠性。

    java学习资料-线程

    对于复杂的多线程场景,可以利用Java并发包提供的高级工具,如CountDownLatch、CyclicBarrier、Semaphore等来简化同步和协调。总之,理解并熟练掌握Java中的线程技术,对于编写高效、稳定的并发程序至关重要。

    【学习笔记】JUC基础基础程序

    Java并发包中的Semaphore(信号量)和LockSupport工具类也常用于线程同步。Semaphore可以限制同时访问特定资源的线程数量,而LockSupport提供底层的线程阻塞和唤醒功能,更底层且灵活性更高。 CountDownLatch和...

    尚硅谷大厂面试题第三季周阳主讲

    【描述】提到的重点在于JUC(Java并发包)中的可重入锁概念,以及与之相关的锁机制,如LockSupport工具类的使用。此外,还提到了LockSupport如何实现线程的阻塞和唤醒,以及AbstractQueuedSynchronizer (AQS) 在锁和...

    java并发技术

    - Java并发包`java.util.concurrent.atomic`提供了原子类,如`AtomicInteger`、`AtomicLong`等。 - 这些类支持无锁操作,可以在高并发环境下高效地更新数值。 #### 4.3 线程安全集合 - `ConcurrentHashMap`:线程...

    java的Lock锁原理详解.docx

    相反,Lock是Java并发包java.util.concurrent.locks中的接口,它提供了更细粒度的锁控制。Lock接口提供了比synchronized更丰富的功能,如尝试获取锁(tryLock)、可中断的锁获取(lockInterruptibly)、超时获取锁...

    Java Unsafe类1

    这些操作在Java并发库的`LockSupport`类中被封装,`LockSupport.park()`和`LockSupport.unpark(Thread)`都是基于`Unsafe`实现的。这种机制在实现自定义同步原语时非常有用。 4. CAS(Compare and Swap)操作: CAS...

    Java多线程编程总结

    Java并发包`java.util.concurrent`提供了许多高级同步工具,如`Semaphore`、`BlockingQueue`、`Condition`、`Atomic`类和`Phaser`等,用于实现更复杂的并发控制和线程协作。 #### 十一、Java线程:大总结 Java多...

    Java并发锁简介-动力节点共9页.pdf.zip

    2. **可重入锁(ReentrantLock)**:这是Java并发包`java.util.concurrent.locks`中的一个锁,相比内置锁具有更高的灵活性。可重入锁支持公平锁和非公平锁模式,允许锁的持有者再次获取锁(即重入),并且可以实现锁...

Global site tag (gtag.js) - Google Analytics