`
春花秋月何时了
  • 浏览: 39547 次
  • 性别: 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类的线程阻塞用法

    LockSupport类提供了park()和unpark()两个方法来实现线程的阻塞和唤醒,下面我们就来详解Java多线程编程中LockSupport类的线程阻塞用法:

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

    本文将详细介绍java线程阻塞中断和LockSupport的使用,需要了解更多的朋友可以参考下

    Java中LockSupport的使用.docx

    LockSupport是JDK1.6中在java.util.concurrent中的子包locks中引入的一个比较底层的工具类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通过...

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

    在Java多线程中,当需要阻塞或者唤醒一个线程时,都会使用LockSupport工具类来完成相应的工作。LockSupport定义了一组公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也因此成为了构建同步...

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

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

    LockSupport

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

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

    Java 多线程与并发(9_26)-JUC锁_ LockSupport详解

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

    主要为大家详细介绍了Java concurrency之LockSupport的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

    主要给大家介绍了关于Java并发编程学习之Unsafe类与LockSupport类源码的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧

    Java多线程和并发知识整理

    一、理论基础 1.1为什么需要多线程 1.2不安全示例 1.3并发问题的根源 1.4JMM 1.5线程安全的分类 1.6线程安全的方法 二、线程基础 2.1状态 2.2使用方式 2.3基础机制 ...十一、JUC锁: LockSupport详解

    LockSupportTest.java

    LockSupport学习demoLockSupport学习demoLockSupport学习demoLockSupport学习demoLockSupport学习demoLockSupport学习demoLockSupport学习demoLockSupport学习demoLockSupport学习demoLockSupport学习...

    JUC学习.docx Java

    (4)start和run:不能直接调用run方法,因为这样并不会启动一个新的线程,依旧是在main线程来执行。相当于普通方法的直接调用 2.常见方法: (1)Sleep:写的位置决定作用于哪个线程,其他线程可以通过interrupt来打断...

    【2018最新最详细】并发多线程教程

    13.LockSupport工具 14.并发容器之ConcurrentHashMap(JDK 1.8版本) 15.并发容器之ConcurrentLinkedQueue 16.并发容器之CopyOnWriteArrayList 17.并发容器之ThreadLocal 18.一篇文章,从源码深入详解ThreadLocal内存...

    java7hashmap源码-to-be-architect:成为Java架构师,你应该学习这些

    Java基础 深入分析 Java SPI 机制和原理 并发编程专题 Executors线程池 线程池ThreadPoolExecutor 并发编程 Lock 锁 Lock 可重入锁Reetrantlock 可重入读写锁ReetrantReadWriteLock Condition ReadWriteLock ...

    高级开发并发面试题和答案.pdf

    synchronize实现基础syn为什么一定有可重入特性; synchronized 实现可重入性; reenlock和synchronize区别; ReentrantLock如何实现可重入性 volatile作用; wait 与 sleep 的有什么不同?回答的要点四个: Thread....

    华为Java高级面试题:用两个线程,一个输出字母,一个输出数字,交替输出1A2B3C4D…26Z

    使用 LockSupport.park()and unpark() public static void main(String[] args) { char[] aI = 1234567.toCharArray(); char[] aC = ABCDEFG.toCharArray(); Lock lock = new ReentrantLock(); Condition

    LockSupportTester.zip

    测试LockSupport,LockSupport基于一个“许可”的概念实现了线程的阻塞与释放,该测试demo就是为了使这个“许可”的概念更加的清晰

    面试必问之AQS原理详解.pdf

    AQS 原理 ...LockSupport.park() 完成,而 LockSupport.park() 则调用 sun.misc.Unsafe.park()本地方法,再进一步,HotSpot 在 Linux 中中通过调用 pthread_mutex_lock 函数把线程交给系统内核进行阻塞。

    ThreadTest.rar

    java并发,主要用于初学者学习,主要案列,Thread.join,ThreadLocal,Lock接口,LockSupport,Condition接口,ConcurrentHashMap的实现原理与使用 Fork/Join 框架,CountDownLatch,CyclicBarrier,Semaphore,...

Global site tag (gtag.js) - Google Analytics