转:LockSupport 及Unsafe

concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS框架借助于两个类:
Unsafe(提供CAS操作) LockSupport(提供park/unpark操作)
Unsafe还可以通过直接操作对象和类的字段偏移地址去获取和更改值,非常强大。

LockSupport

park函数是将当前调用Thread阻塞,而unpark函数则是将指定线程Thread唤醒。
两个重点
(1)操作对象Thread

归根结底,LockSupport.park()和LockSupport.unpark(Thread thread)调用的是Unsafe中的native代码:

//LockSupport中
public static void park() {
        UNSAFE.park(false, 0L);
    }
//LockSupport中
public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

Unsafe类中的对应方法:

//park
public native void park(boolean isAbsolute, long time);
//unpack
public native void unpark(Object var1);

Object类的wait/notify机制相比,park/unpark有两个优点:

  • 以thread为操作对象更符合阻塞线程的直观定义
  • 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

(2)关于“许可”

在上面的文字中,我使用了阻塞和唤醒,是为了和wait/notify做对比。

其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。

有一点比较难理解的,是unpark操作可以再park操作之前。

也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。

但是这个“许可”是不能叠加的,“许可”是一次性的。
比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。

Unsafe.park和Unsafe.unpark的底层实现原理

在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。

源码:
每个Java线程都有一个Parker实例,Parker类是这样定义的:

class Parker : public os::PlatformParker {  
private:  
  volatile int _counter ;  
  ...  
public:  
  void park(bool isAbsolute, jlong time);  
  void unpark();  
  ...  
}  
class PlatformParker : public CHeapObj<mtInternal> {  
  protected:  
    pthread_mutex_t _mutex [1] ;  
    pthread_cond_t  _cond  [1] ;  
    ...  
}  

可以看到Parker类实际上用Posix的mutex,condition来实现的。
在Parker类里的_counter字段,就是用来记录“许可”的。

park 过程

当调用park时,先尝试能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回:

void Parker::park(bool isAbsolute, jlong time) {  
  
  // Ideally we'd do something useful while spinning, such  
  // as calling unpackTime().  
  
  // Optional fast-path check:  
  // Return immediately if a permit is available.  
  // We depend on Atomic::xchg() having full barrier semantics  
  // since we are doing a lock-free update to _counter.  
  
  	if (Atomic::xchg(0, &_counter) > 0) return;  
// 如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,
// 如果是,则把_counter设置为0,unlock mutex并返回:

	ThreadBlockInVM tbivm(jt);  
	if (_counter > 0)  { // no wait needed  
  	_counter = 0;  
  	status = pthread_mutex_unlock(_mutex);  
	// 否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,
	// 如果等待返回,则把_counter设置为0,unlock mutex并返回:
	if (time == 0) {  
  		status = pthread_cond_wait (_cond, _mutex) ;  
	}  
	_counter = 0 ;  
	status = pthread_mutex_unlock(_mutex) ;  
	assert_status(status == 0, status, "invariant") ;  
	OrderAccess::fence();  
}

unpark 过程

当unpark时,则简单多了,直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:

void Parker::unpark() {  
  int s, status ;  
  status = pthread_mutex_lock(_mutex);  
  assert (status == 0, "invariant") ;  
  s = _counter;  
  _counter = 1;  
  if (s < 1) {  
     if (WorkAroundNPTLTimedWaitHang) {  
        status = pthread_cond_signal (_cond) ;  
        assert (status == 0, "invariant") ;  
        status = pthread_mutex_unlock(_mutex);  
        assert (status == 0, "invariant") ;  
     } else {  
        status = pthread_mutex_unlock(_mutex);  
        assert (status == 0, "invariant") ;  
        status = pthread_cond_signal (_cond) ;  
        assert (status == 0, "invariant") ;  
     }  
  } else {  
    pthread_mutex_unlock(_mutex);  
    assert (status == 0, "invariant") ;  
  }  
}  

Unsafe

Unsafe 类就是java提供的,对系统硬件级别的底层操作;Unsafe 的获取方法:Unsafe
位于sun.misc包下,通常eclipse限制了对该类的直接使用,并且也不能通过Unsafe提供的getUnsafe()方法获取到该类的实例,因为你的类不被该类所信任;

@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

在方法上有一个@CallerSensitive注解,该注解表示该方法的调用,需要调用者被该方法信任;那么怎么获取到Unsafe的实例呢?解决方法如下:
利用反射机制,Unsafe中有一个字段名为“theUnsafe”,该字段保存有一个Unsafe的实例,只要获取在该字段上的Unsafe实例就好了,代码如下:

@SuppressWarnings("restriction")
static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException {
    Class<?> cls = sun.misc.Unsafe.class;
    Field[] fields = cls.getDeclaredFields();
    for(Field f : fields) {
        if("theUnsafe".equals(f.getName())) {
            f.setAccessible(true);
            return (sun.misc.Unsafe) f.get(null);
        }
    }
    throw new IllegalAccessException("no declared field: theUnsafe");
}

Unsafe 获取对象字段偏移量,及修改偏移量对应字段的值,代码如下:

import java.lang.reflect.Field;public class TestUnsafe {
    
    static private int number = 5;
    
    private String c;

    @SuppressWarnings({ "restriction" })
    public static void main(String[] args) throws Throwable {
        

        TestUnsafe t = new TestUnsafe();
        
        sun.misc.Unsafe unsafe = getUnsafe();  
        //对象的操作
        //1,获取对象的字段相对该对象地址的偏移量;
        //1.1 静态字段获取 ;说明:静态字段的偏移量相对于该类的内存地址,即相对于 className.class 返回的对象;
        long staticFieldOffset = unsafe.staticFieldOffset(TestUnsafe.class.getDeclaredField("number"));
//1.2 非静态字段 ;说明:该偏移量相对于该类的实例化对象的内存地址,即 new 返回的对象; 这里相对于上面实例化的 t对象
        long unstaticFieldOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("c"));
        
        System.out.println("静态变量相对于类内存地址的偏移量 = " + staticFieldOffset);
        System.out.println("非静态变量相对于实例化对象的偏移量 = " + unstaticFieldOffset);
    
        //修改对象字段的值;
        //1.3 修改非基本数据类型的值,使用:putObject(object , offset , value); 这里修改 实例化对象t对应偏移地址字段的值;
        unsafe.putObject(t, unstaticFieldOffset, "b");
        
        //1.3 修改基本数据类型的值,使用对应类型的put方法,如:int 使用 putInt(object , offset , value);
        unsafe.putInt(TestUnsafe.class, staticFieldOffset, 4);
        
        System.out.println("静态变量被修改后的值 = " + TestUnsafe.number);
        System.out.println("非静态变量被修改后的值 = " + t.c); 
    }
    
   //利用反射获取Unsafe的实例
    @SuppressWarnings("restriction")
    static private sun.misc.Unsafe getUnsafe() throws IllegalArgumentException, IllegalAccessException {
        Class<?> cls = sun.misc.Unsafe.class;
        Field[] fields = cls.getDeclaredFields();
        for(Field f : fields) {
            if("theUnsafe".equals(f.getName())) {
                f.setAccessible(true);
                return (sun.misc.Unsafe) f.get(null);
            }
        }
        throw new IllegalAccessException("no declared field: theUnsafe");
    }
}
 

Unsafe 内存的使用:申请allocateMemory(long)、扩展reallocateMemory(long,long)、销毁freeMemory(long)、插入值putXXX()、获取值getXXX(),示例代码如下:

   //内存使用 
    //说明:该内存的使用将直接脱离jvm,gc将无法管理以下方式申请的内存,以用于一定要手动释放内存,避免内存溢出;
    //2.1 向本地系统申请一块内存地址; 使用方法allocateMemory(long capacity) ,该方法将返回内存地址的起始地址
    long address = unsafe.allocateMemory(8);
    System.out.println("allocate memory address = " + address);
    
    //2.2  向内存地址中设置值;
    //2.2 说明: 基本数据类型的值的添加,使用对应put数据类型方法,如:添加byte类型的值,使用:putByte(内存地址 , 值);
    unsafe.putByte(address, (byte)1);
    
    //2.2 添加非基本数据类型的值,使用putObject(值类型的类类型 , 内存地址 , 值对象);
    unsafe.putObject(Hello.class, address+2, new Hello());
    
    //2.3 从给定的内存地址中取出值, 同存入方法基本类似,基本数据类型使用getXX(地址) ,object类型使用getObject(类类型,地址);
    byte b = unsafe.getByte(address);
    System.out.println(b);
    
    //2.3 获取object类型值
    Hello h = (Hello) unsafe.getObject(Hello.class, address+2);
    System.out.println(h);
    
    //2.4 重新分配内存 reallocateMemory(内存地址 ,大小) , 该方法说明 :该方法将释放掉给定内存地址所使用的内存,并重新申请给定大小的内存;
    // 注意: 会释放掉原有内存地址 ,但已经获取并保存的值任然可使用,原因:个人理解:使用unsafe.getXXX方法获取的是该内存地址的值,
    //并把值赋值给左边对象,这个过程相当于是一个copy过程--- 将系统内存的值 copy 到jvm 管理的内存中;
    long newAddress = unsafe.reallocateMemory(address, 32);
    System.out.println("new address = "+ newAddress);
    //再次调用,内存地址的值已丢失; 被保持与jvm中的对象值不被丢失;
    System.out.println("local memory value =" + unsafe.getByte(address) + " jvm memory value = "+ b);
    
    //2.5 使用申请过的内存;
    //说明: 该方法同reallocateMemory 释放内存的原理一般;
    unsafe.freeMemory(newAddress);
    
    //2.5 put 方法额外说明
    //putXXX() 方法中存在于这样的重载: putXXX(XXX ,long , XXX) ,如:putInt(Integer ,long , Integer) 或者 putObject(Object ,long ,Object)
    //个人理解 : 第一个参数相当于作用域,即:第三个参数所代表的值,将被存储在该域下的给定内存地址中;(此处疑惑:
    //如果unsafe是从操作系统中直接获取的内存地址,那么该地址应该唯一,重复在该地址存储数据,后者应该覆盖前者,但是并没有;应该是jvm有特殊处理,暂未研究深入,所以暂时理解为域;)
    //以下示例可以说明,使用allocateMemory申请的同一地址,并插入不同对象所表示的值,后面插入的值并没有覆盖前面插入的值;
    //
    long taddress = unsafe.allocateMemory(1);
    Hello l = new Hello("l");
    Hello l1 = new Hello("l1");
    unsafe.putObject(l, taddress, l);
    System.out.println(unsafe.getObject(l, taddress));
    unsafe.putObject(l1, taddress, l1);
    System.out.println(unsafe.getObject(l1, taddress));
    System.out.println(unsafe.getObject(l, taddress));
    unsafe.putObject(Hello.class, taddress, new Hello("33"));
    System.out.println(unsafe.getObject(Hello.class, taddress));
    
    unsafe.freeMemory(taddress);

重要的事情说n遍::::Unsafe申请的内存的使用将直接脱离jvm,gc将无法管理Unsafe申请的内存,所以使用之后一定要手动释放内存,避免内存溢出!!!

CAS 操作(CAS,compare and swap的缩写,意:比较和交换):硬件级别的原子性更新变量;在Unsafe 中主要有三个方法:CompareAndSwapInt() ,CompareAndSwapLong() ,CompareAndSwapObject()

//3.0关于并发对变量的原子操作,请查看其它资料;unsafe 提供硬件级别的原子操作CAS方法,如:compareAndSwapInt(Object ,long ,int ,int)
        //说明: 第一个参数:需要更新的对象;第二个参数:偏移地址; 第三个对象:预期在该偏移地址上的当前值,即:getInt(obj,偏移地址) == 预期值; 第四个参数:需要更新的值
        //此类方法,当且仅当当前偏移量的值等于预期值时,才更新为给定值;否则不做任何改变;
        //compareAndSwapObject 和 compareAndSwapLong 与下述示例类似;
        long offset = unsafe.allocateMemory(1);

        unsafe.putInt(Integer.class, offset, 1);

        System.out.println(unsafe.getInt(Integer.class, offset));
boolean updateState = unsafe.compareAndSwapInt(Integer.class, offset, 1, 5);
        System.out.println("update state = "+ updateState +" ; value = " + unsafe.getInt(Integer.class,offset));
        
        unsafe.freeMemory(offset);

线程挂起和恢复,part()、unpart(),代码如下:

 	//4.1  unsafe提供线程挂起和恢复的原语;
    /*    挂起线程,方法如下
     *  part(boolean abs,long timeout) 
     *  方法说明:将当前线程挂起,直到当期时间“到达”(1)timeout描述的时间点,或者等待线程中断或unpark;
     *  (1):注意:这里使用的是到达,即给定的timeout时间是一个时间点,该时间点从1970计数开始;
     *  参数说明:
     *      abs 为false 时,表示timeout以纳秒为单位 ;当为false是,可设置timeout为0,表示永远挂起,直到interrupt 或则 unpart
     *      abs 为true 时,表示timeout以毫秒为单位;注意,经测试在abs为true时,将timeout设置为0,线程会立即返回;
     *      timeout : 指定线程挂起到某个时间点,该时间点从1970计数开始;
     */
    //ex1 :
    Thread thread = new Thread(()->{
        unsafe.park(false, 0);//永远挂起
    });
    thread.start();
    
    /*
     * 4.2 恢复线程,方法如下:
     * unpark(Object thread);
     * 方法说明: 给与传入对象一个运行的许可,即将给定的线程从挂起状态恢复到运行状态;
     * 参数说明:thread :通常是一个线程对象;
     * 特殊说明:unpark 可以在park之前使用,但不论在park方法之前,进行了多少次的调用unpark方法,对于作为参数的thread线程始终将只获得一个运行许可;
     * 即:当park方法调用时,检测到该线程存在一个运行许可,park方法也会立即返回;这种方式在多线程中虽然很灵活,相对于notify/wait的方式,但不建议如此使用;
     */
    //ex2:
    unsafe.unpark(thread);//恢复线程
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值