java.util.concurrent各组件分析 一 sun.misc.Unsafe

时间:2023-03-08 17:42:32

java.util.concurrent各组件分析 一 sun.misc.Unsafe

说到concurrent包也叫并发包,该包下主要是线程操作,方便的进行并发编程,提到并发那么锁自然是不可缺少的,包中的类存在了大量关于锁的操作

因此有必要先了解java中锁的原理,锁的底层就是sun.misc.Unsafe类,这个类可以说是java并发包的基础,基本上并发包的所有组件都是依赖Unsafe来进行底层同步操作的

java不能直接访问操作系统底层,而是通过本地方法来访问,Unsafe类提供了硬件级别的原子操作,在100+的方法中大部分都是native类型的,可以进行底层操作,
比如操作内存、低级同步、CAS方法、操作Class、操作Object等等。实际通过该类就像C、C++一样可以精确操作内存。

但java特点就是面向对象,表面上是不进行类似指针形式的直接内存操作,因此该类是不提倡我们在上层的代码中直接使用的。

1.简单知道可以操作内存
类中有3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存,与C语言中的3个方法是对应的。
再深入就追踪到C的类库中了,不向下看了。

2.与并发编程相关的几类方法
2.1 CAS方法
2.2 通过getBooleanVolatile等方法,定位字段,进行存取
2.2 操作线程的方法,park线程等待,unpark唤醒线程

2.3 CAS方法,主要是compareAndSwapXXX方法
compareAndSwapObject提供了对一个对象引用进行CAS的能力
compareAndSwapInt提供了对一个32位整数进行CAS操作的能力
compareAndSwapLong提供了对64位整数进行CAS操作的能力

	/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

 

CAS操作看起来是很爽的,但是CAS操作会存在ABA问题,简单来说就是某线程进行compareAndSwap,在进行比较时,另一个线程对该内存值进行了两次或以上的更新操作,
但最终还原了值,这时对前一个线程来说相当于没变CAS操作成功。但是这个成功不代表没问题,如果值是对象、链表等其中的属性或某种状态是发生了变化的就有问题了。
解决ABA问题可以使用AtomicStampedReference/AtomicMarkableReference类进行,其带有数据版本号完全避免了ABA问题

2.2 通过getBooleanVolatile等方法,定位字段,进行存取

1.获取内存位置有staticFieldOffset、objectFieldOffset方法实现,方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的。

2.还可以通过下面的一系列,获取内存地址的方法

public native void putObjectVolatile(Object var1, long var2, Object var4);

    public native int getIntVolatile(Object var1, long var2);

    public native void putIntVolatile(Object var1, long var2, int var4);

    public native boolean getBooleanVolatile(Object var1, long var2);

    public native void putBooleanVolatile(Object var1, long var2, boolean var4);

    public native byte getByteVolatile(Object var1, long var2);

    public native void putByteVolatile(Object var1, long var2, byte var4);

    public native short getShortVolatile(Object var1, long var2);

    public native void putShortVolatile(Object var1, long var2, short var4);

    public native char getCharVolatile(Object var1, long var2);

    public native void putCharVolatile(Object var1, long var2, char var4);

    public native long getLongVolatile(Object var1, long var2);

    public native void putLongVolatile(Object var1, long var2, long var4);

    public native float getFloatVolatile(Object var1, long var2);

    public native void putFloatVolatile(Object var1, long var2, float var4);

    public native double getDoubleVolatile(Object var1, long var2);

    public native void putDoubleVolatile(Object var1, long var2, double var4);

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

3.数组类型有BASE_OFFSET结尾的这些常量辅助去找位置
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
public static final int ARRAY_BYTE_BASE_OFFSET;
public static final int ARRAY_SHORT_BASE_OFFSET;
public static final int ARRAY_CHAR_BASE_OFFSET;

2.3 操作线程的方法,park线程等待,unpark唤醒线程
park 让线程进入等待,并释放锁。调用后线程将一直阻塞知道超时或中断等条件出现。
unpark 唤醒等待的线程使其恢复正常。

在使用Unsafe时,我们需要Unsafe对象的实例。但Unsafe unsafe = new Unsafe()是不行的,其构造方法是私有的,它也有一个静态的getUnsafe()方法,
但如果直接调用Unsafe.getUnsafe(),会报SecurityException异常,只能从受信任的代码中使用这个方法。
这个受信任就比较麻烦,但Unsafe类包含一个私有的、名为theUnsafe的实例,可以通过Java反射等窃取该变量。
说这么多我们就会发现Unsafe在刻意隐藏,也就前面说的不推荐上传应用使用该类。

并发包内提供了LockSupport封装了Unsafe对象的类,一般我们都用这个封装类。

public class LockSupport {
private LockSupport() {} // Cannot be instantiated.
private static final sun.misc.Unsafe UNSAFE;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
} catch (Exception ex) { throw new Error(ex); }
} private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
} 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);
} public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
} public class Thread{
volatile Object parkBlocker;
}
主要包含的点:
1.利用Unsafe直接操作内存来设置blocker
parkBlocker是thread中的volatile类型的变量。UNSAFE.objectFieldOffset可以获得这里是blocker的内存偏移offset,即物理内存位置,和c++就一模一样了。
setBlocker方法是把arg对象设置到Thread的parkBlocker属性上。
parkBlocker是用来表明本线程是在哪个对象上阻塞 2.park有两个方法,带参数的就是指明锁对象,即表明该线程锁在那个对象上。不带参数就不表明了。 3.unpark直接唤醒