并发之Atomic类与Unsafe类

1、前言
 
Java中 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 所谓原子类说简单点就是具有原子/原子操作特征的类。Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。
在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。
 
2、 原子类分类
 
JUC包下面原子类分为四类:
 
2.1、基本类型
使用原子的方式更新基本类型
  • AtomicInteger:整形原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean:布尔型原子类
2.2、数组类型
使用原子的方式更新数组里的某个元素
  • AtomicIntegerArray:整形数组原子类
  • AtomicLongArray:长整形数组原子类
  • AtomicReferenceArray:引用类型数组原子类
2.3、引用类型
  • AtomicReference:引用类型原子类
  • AtomicStampedReference:原子更新引用类型里的字段原子类
  • AtomicMarkableReference :原子更新带有标记位的引用类型
2.4、对象的属性修改类型
  • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  • AtomicLongFieldUpdater:原子更新长整形字段的更新器
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
3、AtomicInteger的使用
 
常用方法:
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

 

AtomicInteger使用:使用AtomicInteger之后名不对increment方法加锁也会保证线程安全。

class AtomicIntegerTest {
        private AtomicInteger count = new AtomicInteger();
      //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。
        public void increment() {
                  count.incrementAndGet();
        }
       public int getCount() {
                return count.get();
       }
}

 

3.1、AtomicInteger实现原理

//setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static
{
    try
    {
        valueOffset = unsafe.objectFieldOffset
        (AtomicInteger.class.getDeclaredField("value"));
    }
    catch (Exception ex)
    {
        throw new Error(ex);
    }
}
private volatile int value;

AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。

4、数组类型原子类介绍

使用原子的方式更新数组里的某个元素
  • AtomicIntegerArray:整形数组原子类
  • AtomicLongArray:长整形数组原子类
  • AtomicReferenceArray:引用类型数组原子类
上面三个类提供的方法几乎相同,所以我们这里以AtomicIntegerArray为例子来介绍。
public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

  

使用:
public class AtomicIntegerArrayTest {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int temvalue = 0;
        int[] nums = {1, 2, 3, 4, 5, 6};
        AtomicIntegerArray i = new AtomicIntegerArray(nums);
        for (int j = 0; j < nums.length; j++) {
            System.out.print(i.get(j) + " ");
        }
        temvalue = i.getAndSet(0, 2);
        System.out.println("\ntemvalue:" + temvalue + ";  i:" + i);
        temvalue = i.getAndIncrement(0);
        System.out.println("temvalue:" + temvalue + ";  i:" + i);
        temvalue = i.getAndAdd(0, 5);
        System.out.println("temvalue:" + temvalue + ";  i:" + i);
    }
}
输出:
1 2 3 4 5 6
temvalue:1;  i:[2, 2, 3, 4, 5, 6]
temvalue:2;  i:[3, 2, 3, 4, 5, 6]
temvalue:3;  i:[8, 2, 3, 4, 5, 6]

 

5、引用类型原子类

 
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类。
  • AtomicReference:引用类型原子类
  • AtomicStampedReference:原子更新引用类型里的字段原子类
  • AtomicMarkableReference :原子更新带有标记位的引用类型
上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。
AtomicReference() // 使用 null 初始值创建新的 AtomicReference。
AtomicReference(V initialValue) // 使用给定的初始值创建新的 AtomicReference。
boolean compareAndSet(V expect, V update)//如果当前值==预期值,则以原子方式将该值设置为给定的更新值。
V get() // 获取当前值。
V getAndSet(V newValue)   //以原子方式设置为给定值,并返回旧值。
void lazySet(V newValue)  //最终设置为给定值。
void set(V newValue)// 设置为给定值。
String toString()// 返回当前值的字符串表示形式。
boolean weakCompareAndSet(V expect, V update)// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
 
使用范例如下:
class Worker{
    private String name;
    private int age;
    public Worker(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
public class TestAtomicReference
{
    public static void main(String[] args)
    {
        AtomicReference<Worker> atomicReference = new AtomicReference<Worker>();
        Worker worker1 = new Worker("zhangsan",30);
        atomicReference.set(worker1);
        Worker worker2 = new Worker("lisi",35);
 
        atomicReference.compareAndSet(worker1,worker2);
        System.out.print(atomicReference.get().getAge() + " ");
        System.out.println(atomicReference.get().getName());
    }
}
输出:
35
lisi

  

6、对象的属性修改类型原子类

 
如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。
AtomicIntegerFieldUpdater:原子更新整形字段的更新器
AtomicLongFieldUpdater:原子更新长整形字段的更新器
AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
 
要想原子地更新对象的属性需要两步。
第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
第二步,更新的对象属性必须使用 public volatile 修饰符。
 
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerFieldUpdater为例子来介绍。
 
不同于以往的几个原子更新类,对于 AtomicIntegerFieldUpdater 的使用稍微有一些限制和约束,约束如下:
1、字段必须是 volatile 类型的,在线程之间共享变量时保证立即可见。
2、字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
3、只能是实例变量,不能是类变量,也就是说不能加static关键字。
4、只能是可修改变量,不能是final变量,因为final的语句就是不可修改。实际上final的语义与volatile是有冲突的,两个关键字不能同时存在。
5. 对于AtomicIntegerFieldUpdate和AtomicLongFieldUpdate只能修改 int/long 类型的字段,不能修改包装类型。如果要修改包装类型就需要使用AtomicReferenceFieldUpdate。
 
方法列表:
// 受保护的无操作构造方法,供子类使用。
protected AtomicIntFieldUpdater()
// 以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
int addAndGet(T obj, int delta)
// 如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract boolean compareAndSet(T obj, int expect, int update)
// 以原子方式将此更新器管理的给定对象字段当前值减 1。
int decrementAndGet(T obj)
// 获取此更新器管理的在给定对象的字段中保持的当前值。
abstract int get(T obj)
// 以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
int getAndAdd(T obj, int delta)
// 以原子方式将此更新器管理的给定对象字段当前值减1。
int getAndDecrement(T obj)
// 以原子方式将此更新器管理的给定对象字段的当前值加1。
int getAndIncrement(T obj)
// 将此更新器管理的给定对象的字段以原子方式设置为给定值,并返回旧值。
int getAndSet(T obj, int newValue)
// 以原子方式将此更新器管理的给定对象字段当前值加 1。
int incrementAndGet(T obj)
// 最后将此更新器管理的给定对象的字段设置为给定更新值。
abstract void lazySet(T obj, int newValue)
// 为对象创建并返回一个具有给定字段的更新器。
static <U> AtomicIntFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName)
// 将此更新器管理的给定对象的字段设置为给定更新值。
abstract void set(T obj, int newValue)
// 如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract boolean weakCompareAndSet(T obj, int expect, int update)

 

测试例子:

class Workernew {
    private String name;
    public volatile int age;
    public Workernew(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
public class TestAtomicIntegerFieldUpdater
{
    public static void main(String[] args)
    {
        AtomicIntegerFieldUpdater<Workernew> atomicIntegerFieldUpdater  =                                AtomicIntegerFieldUpdater.newUpdater(Workernew.class, "age");
 
        Workernew worker = new Workernew("wangwu", 30);
        System.out.println(atomicIntegerFieldUpdater.getAndIncrement(worker));
        System.out.println(atomicIntegerFieldUpdater.get(worker));
    }
}
输出:
30
31

 

7、Unsafe类
实际上Atomic包里的类基本都是使用Unsafe实现的包装类。也就是上面的原子类实现过程中都会用到Unsafe类。Java中的Unsafe类提供了类似C++手动管理内存的能力。Unsafe类,全限定名是sun.misc.Unsafe,从名字可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。Unsafe类是"final"的,不允许继承。且构造函数是private的,无法在外部对其进行实例化,源码如下:
public final class Unsafe
{
    private static final Unsafe theUnsafe;
    public static final int INVALID_FIELD_OFFSET = -1;
    private static native void registerNatives();
    // 构造函数是private的,不允许外部实例化
    private Unsafe(){
    }
    @CallerSensitive
    public static Unsafe getUnsafe(){
        Class var0 = Reflection.getCallerClass();
        // 仅在引导类加载器`BootstrapClassLoader`加载时才合法
        if(!VM.isSystemDomainLoader(var0.getClassLoader())){
            throw new SecurityException("Unsafe");
        }
        else{
            return theUnsafe;
        }
    }
}

那如若想使用这个类,该如何获取其实例?有如下两个可行方案。

 
其一,从getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。
java -Xbootclasspath/a: ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径
 
其二、通过反射来获得Unsafe:
public Unsafe getUnsafe() throws IllegalAccessException {
    try{
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        return unsafe;
    }
    catch (Exception e){
        log.error(e.getMessage(), e);
        return null;
    }
}

 

7.1 Unsafe的主要功能

 
7.2、普通读写
 
通过Unsafe可以读写一个类的属性,即使这个属性是私有的,也可以对这个属性进行读写。读写一个Object属性的相关方法:
 
public native int getInt(Object var1, long var2);//用于从对象的指定偏移地址处读取一个int
public native void putInt(Object var1, long var2, int var4);//用于在对象指定偏移地址处写入一个int

Unsafe还可以直接在一个地址上读写:

public native byte getByte(long var1);//从指定内存地址处开始读取一个byte
public native void putByte(long var1, byte var3);//用于从指定内存地址写入一个byte
 
7.3、volatile读写
 
普通的读写无法保证可见性和有序性,而volatile读写就可以保证可见性和有序性。
public native int getIntVolatile(Object var1, long var2);//用于在对象指定偏移地址处volatile读取一个int
public native void putIntVolatile(Object var1, long var2, int var4);//用于在对象指定偏移地址处volatile写入一个int

volatile读写相对普通读写是更加昂贵的,因为需要保证可见性和有序性,而与volatile写入相比putOrderedXX写入代价相对较低,putOrderedXX写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。

 
7.4、有序写入
有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。
public native void putOrderedObject(Object var1, long var2, Object var4);
public native void putOrderedInt(Object var1, long var2, int var4);
public native void putOrderedLong(Object var1, long var2, long var4);

7.5、直接内存操作

Java不可以直接对内存进行操作,对象内存的分配和回收都是由JVM实现的。但是Unsafe为Java中提供了直接操作内存的能力。Unsafe类中提供的3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存。

// 分配内存,相对于C++上的malloc函数
public native long allocateMemory(long var1);
// 重新分配内存
public native long reallocateMemory(long var1, long var3);
// 内存初始化
public native void setMemory(long var1, long var3, byte var5);
// 内存拷贝
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 清除内存
public native void freeMemory(long var1);

通常,我们在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内存。与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法。

使用堆外内存的原因:
1、对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模,从而在GC时减少回收停顿对于应用的影响。
2、提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。
 
7.5.1、典型应用
 
DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。
 
下图为DirectByteBuffer构造函数,创建DirectByteBuffer的时候,通过Unsafe.allocateMemory分配内存、Unsafe.setMemory进行内存初始化,而后构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放。
那么如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放呢?
 
 Cleaner继承自Java四大引用类型之一的虚引用PhantomReference(众所周知,无法通过虚引用获取与之关联的对象实例,且当对象仅被虚引用引用时,在任何发生GC的时候,其均可被回收),通常PhantomReference与引用队列ReferenceQueue结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能。如下图所示,当某个被Cleaner引用的对象将被回收时,JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表中,等待Reference-Handler进行相关处理。其中,Reference-Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作。
所以当DirectByteBuffer仅被Cleaner引用(即为虚引用)时,其可以在任意GC时段被回收。当DirectByteBuffer实例对象被回收时,在Reference-Handler线程操作中,会调用Cleaner的clean方法根据创建Cleaner时传入的Deallocator来进行堆外内存的释放。
 
7.6、CAS相关
JUC中大量运用了CAS操作,可以说CAS操作是JUC的基础,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作:
 
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为U,否则啥都不做。
CAS一般用于乐观锁,它在Java中有广泛的应用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS来实现乐观锁。
 
7.6.1、典型应用
 
CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等实现上有非常广泛的应用。如下图所示,AtomicInteger的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。
下图为某个AtomicInteger对象自增操作前后的内存示意图,对象的基地址baseAddress="0x110000",通过baseAddress+valueOffset得到value的内存地址valueAddress="0x11000c";然后通过CAS进行原子性的更新操作,成功则返回,否则继续重试,直到更新成功为止。
 
 
 
7.7偏移量相关
 
Unsafe类可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的;
字段的定位:
Java中对象的字段的定位可能通过staticFieldOffset方法实现,该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的。
getIntVolatile方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。
getLong方法获取对象中offset偏移地址对应的long型field的值
 
数组元素定位:
Unsafe类中有很多以BASE_OFFSET结尾的常量,比如ARRAY_INT_BASE_OFFSET,ARRAY_BYTE_BASE_OFFSET等,这些常量值是通过arrayBaseOffset方法得到的。arrayBaseOffset方法是一个本地方法,可以获取数组第一个元素的偏移地址。Unsafe类中还有很多以INDEX_SCALE结尾的常量,比如 ARRAY_INT_INDEX_SCALE , ARRAY_BYTE_INDEX_SCALE等,这些常量值是通过arrayIndexScale方法得到的。arrayIndexScale方法也是一个本地方法,可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用,可以定位数组中每个元素在内存中的位置。
public native long staticFieldOffset(Field var1);
public native long objectFieldOffset(Field var1);
public native Object staticFieldBase(Field var1);
//返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> var1);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> var1);

staticFieldOffset方法用于获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量。objectFieldOffset方法用于获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量。staticFieldBase方法用于返回Field所在的对象。arrayBaseOffset方法用于返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量。arrayIndexScale方法用于计算数组中第一个元素所占用的内存空间。

 
这两个与数据操作相关的方法,在java.util.concurrent.atomic 包下的AtomicIntegerArray(可以实现对Integer数组中每个元素的原子性操作)中有典型的应用,如下图AtomicIntegerArray源码所示,通过Unsafe的arrayBaseOffset、arrayIndexScale分别获取数组首元素的偏移地址base及单个元素大小因子scale。后续相关原子性操作,均依赖于这两个值进行数组中元素的定位,getAndAdd方法即通过checkedByteOffset方法获取某数组元素的偏移地址,而后通过CAS实现原子性操作。
 
 
7.8、线程调度
 
将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()和unpark()方法。
 
public class LockSupport
{
    // 唤醒线程
    public static void unpark(Thread thread){
        if (thread != null)
            unsafe.unpark(thread);
    }
    // 挂起线程
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker); // 通过Unsafe的putObject方法设置阻塞阻塞当前线程的blocker
        UNSAFE.park(false, 0L); // 通过Unsafe的park方法来阻塞当前线程,注意此方法将当前线程阻塞后,当前线程就不会继续往下走了,直到其他线程unpark此线程
        setBlocker(t, null); // 清除blocker
    }
    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);
    }
    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);
    }
}

park方法和unpark方法相信看过LockSupport类的都不会陌生,这两个方法主要用来挂起和唤醒线程。LockSupport中的park和unpark方法正是通过Unsafe来实现的,Unsafe中的unpark和park如下:

public native void unpark(Object var1);
public native void park(boolean var1, long var2);
public native void monitorEnter(Object var1);
public native void monitorExit(Object var1);
public native boolean tryMonitorEnter(Object var1);

如上源码说明中,方法park、unpark即可实现线程的挂起与恢复,将一个线程进行挂起是通过park方法实现的,调用park方法后,线程将一直阻塞直到超时或者中断等条件出现;unpark可以终止一个挂起的线程,使其恢复正常。

 
典型应用
 
Java锁和同步器框架的核心类AbstractQueuedSynchronizer,就是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的,而LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现。
monitorEnter方法和monitorExit方法用于加锁,Java中的synchronized锁就是通过这两个指令来实现的。
 
7.9、类加载
 
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
public native boolean shouldBeInitialized(Class<?> var1);
public native void ensureClassInitialized(Class<?> var1);

defineClass方法定义一个类,用于动态地创建类。

defineAnonymousClass用于动态的创建一个匿名内部类。
allocateInstance方法用于创建一个类的实例,但是不会调用这个实例的构造方法,如果这个类还未被初始化,则初始化这个类。
shouldBeInitialized方法用于判断是否需要初始化一个类。
ensureClassInitialized方法用于保证已经初始化过一个类。
 
7.10、内存屏障
 
在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
 
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前,保证在这个屏障之前的所有读操作都已经完成。
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前,保证在这个屏障之前的所有写操作都已经完成。
public native void storeFence();
//内存屏障,禁止load、store操作重排序,保证在这个屏障之前的所有读写操作都已经完成。
public native void fullFence();
 
7.10.1、典型应用
 
在Java 8中引入了一种锁的新机制——StampedLock,它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现,这种乐观读锁类似于无锁的操作,完全不会阻塞写线程获取写锁,从而缓解读多写少时写线程“饥饿”现象。由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题,所以当使用StampedLock的乐观读锁时,需要遵从如下图用例中使用的模式来确保数据的一致性。
 
 
 
posted @ 2020-04-30 22:13  jrliu  阅读(608)  评论(0编辑  收藏  举报