JUC - 基本线程阻塞原语LockSupport

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lipinganq/article/details/80099255

一.概述

LockSupport 和 CAS 是Java并发包中很多并发工具控制机制的基础,它们底层其实都是依赖Unsafe实现。
LockSupport是创建锁和其他同步类的基本线程阻塞原语,park 和 unpark 方法提供了阻塞和解除阻塞线程的有效方法
LockSupport类以及每个使用它的线程和一个permit({@link java.util.concurrent.Semaphore})相关联:

  1. 如果permit许可可用, permit最多一个, permit相当于0、1开关,默认是0
  2. 调用一次unpark()permit就+1,permit变成1,再次调用unpark()多次不会累积,即调用多次permit任然为1
  3. 调用一次 park() permit就-1,permit变成0,再次调用 park() 线程就会阻塞(因为没有1了,会等在这儿,直到有1)

二.LockSupport类讲解

1. 私有构造函数

构造函数私有,说明LockSupport不能初始化

private LockSupport() {}

2. 静态代码块

  1. UNSAFE:可以直接操控内存,被JDK广泛用于自己的包中,但是不建议在生产环境中使用这个类。因为这个API十分不安全、不轻便、而且不稳定
  2. UNSAFE.objectFieldOffset(Field field)可以准确获取某个字段相对于对象的起始内存地址的字节偏移量
  3. parkBlocker是Thread类中的成员变量,记录了当前线程阻塞时是被谁阻塞的,用于线程监控和分析,这里利用反射获取Thread类中的parkBlocker字段信息
private static final sun.misc.Unsafe UNSAFE;
   private static final long parkBlockerOffset;
   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;
           parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
           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); }
   }

3. 设置/获取blocker对象

  1. parkBlockerOffset是Threa中parkBlocker字段的偏移量,在设置/获取parkBlocker会用到
  2. parkBlocker只有在线程被阻塞时才会被赋值,由于线程被阻塞了,需要通过这种内存方式获取,不能直接调用Thread中get方法获取,线程是不会回应调用的
   /**
    * 设置当前线程被阻塞时是被谁阻塞的,blocker用于线程监控和分析
    * @param t 当前被阻塞线程
    * @param arg 阻塞当前线程的对象
    * */
   private static void setBlocker(Thread t, Object arg) {
       UNSAFE.putObject(t, parkBlockerOffset, arg);
   }


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

4. 阻塞/解除阻塞线程方法

4.1 解除阻塞unpark
  • unpark(Thread thread):解除阻塞
    如果给定线程thread的许可permit尚不可用,使其可用,permit会加1,但是不会累加
    如果给定线程thread是被阻塞,则解除其阻塞状态
  /**
    * 解除线程thread的阻塞
    * @param thread 阻塞的线程
    * */
   public static void unpark(Thread thread) {
       if (thread != null)
           UNSAFE.unpark(thread);
   }
4.2 阻塞线程park
  • park() : 阻塞当前线程
  • park(Object blocker)
  • parkNanos(long nanos):阻塞当前线程,最长阻塞nanos纳秒
  • parkNanos(Object blocker, long nanos)
  • parkUntil(long deadline):阻塞当前线程到指定时间deadline
  • parkUntil(Object blocker, long deadline)
    1. 如果许可permit可用(即permit>0),则使用许可(permit-1),并且调用立即返回
    2. 如果许可permit不可用,阻塞当前线程
    3. blocker参数表示线程被谁阻塞,用于线程监控和分析,建议使用带blocker参数的方法,而不是不带blocker参数的原始方法,在锁实现中blocker参数一般为this
   public static void park() {
       UNSAFE.park(false, 0L);
   }

   public static void parkNanos(long nanos) {
       if (nanos > 0)
           UNSAFE.park(false, nanos);
   }

   public static void parkUntil(long deadline) {
       UNSAFE.park(true, deadline);
   }

   /**
    * 阻塞当前线程,线程将一直阻塞直到超时或者中断等条件出现
    * 1. 记录当前线程被blocker阻塞
    * 2. 阻塞当前线程
    * 3. 线程t解除阻塞后清除blocker
    * */
   public static void park(Object blocker) {
       Thread t = Thread.currentThread();
       setBlocker(t, blocker);
       UNSAFE.park(false, 0L);
       setBlocker(t, null);
   }

   /**
    * 阻塞当前线程,最长等待时间不超过nanos纳秒
    *
    * @param blocker
    * @param nanos 等待纳秒
    * */
   public static void parkNanos(Object blocker, long nanos) {
       if (nanos > 0) {
           Thread t = Thread.currentThread();
           setBlocker(t, blocker);
           UNSAFE.park(false, nanos);
           setBlocker(t, null);
       }
   }

   public static void parkUntil(Object blocker, long deadline) {
       Thread t = Thread.currentThread();
       setBlocker(t, blocker);
       UNSAFE.park(true, deadline);
       setBlocker(t, null);
   }
4.3 唤醒线程

线程调用LockSupport.park(this)被挂起,下面三种情况会唤醒线程:
1. 其他线程中以被挂起线程为目标调用unpark
2. 其他线程中中断当前就线程
3. 虚假呼叫,即无理由返回

三. 示例说明

1. 阻塞线程

package com.test;

import java.util.concurrent.locks.LockSupport;

public class TestPart {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread() {
            public void run() {
                System.out.println();
                System.out.println("子线程开始执行:");
                System.out.println("子线程开始阻塞");

                LockSupport.park(); // 阻塞子线程

                System.out.println();
                System.out.println("子线程执行完成");
            }
        };

        System.out.println("主线程开始执行");
        thread.start();

        System.out.println("主线程阻塞5秒");
        Thread.sleep(5000);

        System.out.println();
        System.out.println("主线程睡眠完成");
        System.out.println();

        System.out.println("子线程解除阻塞");
        LockSupport.unpark(thread); // 子线程解除阻塞

        System.out.println();
        System.out.println("主线程执行完成");
    }
}

执行结果:
这里写图片描述

2. 阻塞线程最多等待纳秒

package com.test;

import java.util.concurrent.locks.LockSupport;

/**
 * 阻塞当前线程,是否真的阻塞取决于permit是否有效
 * 1. 每个线程都有个相关的permit(在{@link java.util.concurrent.Semaphore}类中)
 * 2. 如果许可证可用, permit最多一个, permit相当于0、1开关,默认是0
 * 3. 调用一次unpark()permit就+1,permit变成1,再次调用unpark()多次不会累积,即调用多次permit任然为1
 * 4. 调用一次  park() permit就-1,permit变成0,再次调用 park() 线程就会阻塞(因为没有1了,会等在这儿,直到有1)
 * */
public class TestPartNanos {
    public static void main(String[] args) {
        /**
         * 1. 当前permit = 0
         * 2. parkNanos: 没有1可减,阻塞
         * 3. 等待5秒后才继续运行
         * */ 
        System.out.println("开始parkNanos: " + System.currentTimeMillis());
        LockSupport.parkNanos(5000000000L); // 当前线程阻塞5秒
        System.out.println("结束parkNanos: " + System.currentTimeMillis());
        System.out.println();

        /**
         * 1. 当前permit = 0
         * 2. unpark: permit = 1
         * 3. parkNanos : permit = 0, 不会阻塞,继续运行
         * */
        System.out.println("开始parkNanos: " + System.currentTimeMillis());
        LockSupport.unpark(Thread.currentThread());
        LockSupport.parkNanos(5000000000L);
        System.out.println("结束parkNanos: " + System.currentTimeMillis());
        System.out.println();

        /**
         * 1. 当前permit = 0
         * 2. unpark: permit = 1
         * 3. unpark: permit = 1, 多次调用unpark不会累积
         * 4. parkNanos : permit = 0
         * 5. parkNanos : 没有1可减,阻塞
         * */
        System.out.println("开始parkNanos: " + System.currentTimeMillis());
        LockSupport.unpark(Thread.currentThread());
        LockSupport.unpark(Thread.currentThread());
        LockSupport.parkNanos(5000000000L);
        System.out.println("首次parkNanos: " + System.currentTimeMillis());
        LockSupport.parkNanos(5000000000L);
        System.out.println("结束parkNanos: " + System.currentTimeMillis());
    }
}

执行结果:
这里写图片描述

3. 阻塞线程直到指定时间

package com.test;

import java.util.Date;
import java.util.concurrent.locks.LockSupport;

/**
 * parkUntil(long deadline):将当前线程一直阻塞到指定的时间
 * */
public class TestPartUntil {
    public static void main(String[] args) {
        Long start = System.currentTimeMillis();
        System.out.println("开始parkUntil : " + start + "L, 当前时间:" + (new Date(start)));
        System.out.println();

        Long deadline = 1524737607772L;
        System.out.println("线程一直阻塞到 : " + deadline + "L, 阻塞时间:"+ (new Date(deadline)));
        LockSupport.parkUntil(deadline);

        System.out.println();
        Long end = System.currentTimeMillis();
        System.out.println("结束parkUntil : " + end + "L, 当前时间:" + (new Date(end)));
    }
}

执行结果:
这里写图片描述

四. 总结

  • park 和 unpark 方法提供了阻塞和解除阻塞线程的有效方法
  • 由于permit的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性
  • 如果许可permit可用,调用part会使用permit,并且调用立即返回
  • 如果许可permit不可用,调用part会阻塞线程
  • part方法不会报告什么造成了此方法的返回,所以调用者应该重新检查最先导致线程暂停的条件。例如调用者可以确定线程的中断状态,或返回时的当前时间

五. LockSupport源码

package java.util.concurrent.locks;
import sun.misc.Unsafe;

public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.

    /**
     * UNSAFE:可以直接操控内存,被JDK广泛用于自己的包中,但是不建议在生产环境中使用这个类。因为这个API十分不安全、不轻便、而且不稳定
     * UNSAFE.objectFieldOffset(Field field):可以准确获取某个字段相对于对象的起始内存地址的字节偏移量
     * parkBlocker:是Thread类中的成员变量,记录了当前线程阻塞时是被谁阻塞的,用于线程监控和分析,这里利用反射获取Thread类中的parkBlocker字段信息
     * */
    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;
    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;
            parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
            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); }
    }

    /**
     * 设置当前线程被阻塞时是被谁阻塞的,blocker用于线程监控和分析
     * @param t 当前被阻塞线程
     * @param arg 阻塞当前线程的对象
     * */
    private static void setBlocker(Thread t, Object arg) {
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }


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

    /**
     * 解除线程thread的阻塞
     * @param thread 阻塞的线程
     * */
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

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

    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

    /**
     * 阻塞当前线程,线程将一直阻塞直到超时或者中断等条件出现
     * 1. 记录当前线程被blocker阻塞
     * 2. 阻塞当前线程
     * 3. 线程t解除阻塞后清除blocker
     * */
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

    /**
     * 阻塞当前线程,最长等待时间不超过nanos纳秒
     *
     * @param blocker
     * @param nanos 等待纳秒
     * */
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }

    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }

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

猜你喜欢

转载自blog.csdn.net/lipinganq/article/details/80099255
今日推荐