java多线程-Unsafe

Unsafe类解析

Unsafe类简介

  Unsafe类位于sun.misc包下,Unsafe类可以直接操作内存,并提供了无锁并发的实现方式CAS(compare and swap),juc包下的大部分工具都是基于该类实现,所以要想深入理解juc必须先要了解Unsafe。然而Java官方并不建议使用该类,所以我们无法在程序进行调用,需要通过反射机制进行调用。

Unsafe类使用

  查看源码,会发现该类的构造方法是private的,这意味这我们无法通过直接创建该类的示例。但是其内部有这样一个类变量,并且提供了访问方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	
private static final Unsafe theUnsafe;

....

private Unsafe() {
}

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

我们先尝试直接调用getUnsafe方法

1
2
3
4
5
6
7
8
9

public void testUnsafe() {

Unsafe.getUnsafe();

}

Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)

结果发现程序报错,java不允许我们直接调用该类,所以这时候需要采用特殊手段,即通过反射方式获取theUsafe类变量。

1
2
3
4
5
6
7
8

public void testUnsafe() throws NoSuchFieldException, IllegalAccessException {

Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);

}

常用方法

CAS

  该类最重要的方法就是CAS相关方法,通过这些方法可以实现无锁并发。

  锁可以按照策略分为乐观锁和悲观锁,悲观锁认为每次访问共享资源都会发生冲突,所以需要对每一次数据访问加锁。而乐观锁认为共享资源的访问不会总是发生冲突,没有冲突的时候线程继续执行,一旦发生冲突,通过CAS技术保证线程安全。

  CAS的核心思想是维护三个值,即待更新变量、预期值、新值,如果待更新变量的值等于预期值,则将其更新为新值,否则说明该值已经被其他线程修改,此时不执行更新操作。

  Unsafe中,比较常用的cas方法有

1
2
3
4
5
6
//var1是待更新对象,var2是对象内存的偏移量,通过偏移量获取该字段的值,var4是预期值,var5是要设置的值
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);

  1.8中还新增了下面几个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//给定对象和字段偏移量,将其增加var4,这里需要注意,是先get再add,返回值是get的值,不返回add后的值
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}

//同上,只不过把类型变成long
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

return var6;
}

//给定对象和字段偏移量,将其设置为var4,这里需要注意,是先get再set,返回值是get的值,不返回set后的值
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));

return var5;
}

//同上,只不过把类型变成long
public final long getAndSetLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var4));

return var6;
}

public final Object getAndSetObject(Object var1, long var2, Object var4) {
Object var5;
do {
var5 = this.getObjectVolatile(var1, var2);
} while(!this.compareAndSwapObject(var1, var2, var5, var4));

return var5;
}

类、对象、变量操作方法

  上述方法中都用到了偏移量,那么偏移量是如何获取的?Unsafe类中提供了很多关于对象、对象、变量的操作方法,可以让我们方便地获取目标字段的偏移量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//获取静态属性偏移量,用于类中静态属性的读写
public native long staticFieldOffset(Field var1);

//获取字段var1在示例对象中的偏移量
public native long objectFieldOffset(Field var1);

//返回值是f.getDeclaringClass()
public native Object staticFieldBase(Field var1);

//获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量的内存地址,通过偏移量便可得到该对象的变量,进行各种操作
public native int getInt(Object var1, long var2);

//设置给定对象上偏移量的int值
public native void putInt(Object var1, long var2, int var4);

//获得给定对象偏移量上的引用类型的值
public native Object getObject(Object var1, long var2);

//设置给定对象偏移量上的引用类型的值
public native void putObject(Object var1, long var2, Object var4);

//其他基本数据类型(long,char,byte,float,double)的操作与getInthe及putInt相同

//设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见
public native void putIntVolatile(Object var1, long var2, int var4);

//获得给定对象的指定偏移量offset的int值,使用volatile语义,总能获取到最新的int值。
public native int getIntVolatile(Object var1, long var2);

//其他基本数据类型(long,char,byte,float,double)的操作与putIntVolatile及getIntVolatile相同,引用类型putObjectVolatile也一样。

//与putIntVolatile一样,但要求被操作字段必须有volatile修饰
public native void putOrderedInt(Object var1, long var2, int var4);

内存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

//分配内存指定大小的内存
public native long allocateMemory(long bytes);

//根据给定的内存地址address设置重新分配指定大小的内存
public native long reallocateMemory(long address, long bytes);

//用于释放allocateMemory和reallocateMemory申请的内存
public native void freeMemory(long address);

//将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
public native void setMemory(Object o, long offset, long bytes, byte value);

//设置给定内存地址的值
public native void putAddress(long address, long x);

//获取指定内存地址的值
public native long getAddress(long address);

//设置给定内存地址的long值
public native void putLong(long address, long x);

//获取指定内存地址的long值
public native long getLong(long address);

//设置或获取指定内存的byte值
public native byte getByte(long address);

public native void putByte(long address, byte x);
//其他基本数据类型(long,char,float,double,short等)的操作与putByte及getByte相同

//操作系统的内存页大小
public native int pageSize();

创建对象

  Unsafe还提供了新建对象的方法,需要注意的是该方法并不会调用对象的构造方法。所以创建对象的方式一共有5种,并不是流传最广的4种。

1
2

public native Object allocateInstance(Class cls) throws InstantiationException;

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);


//通过allocateInstance创建对象
User user = (User) unsafe.allocateInstance(User.class);

Class userClass = user.getClass();
Field age = userClass.getDeclaredField("age");
Field name = userClass.getDeclaredField("name");
Field id = userClass.getDeclaredField("id");

unsafe.putInt(user, unsafe.objectFieldOffset(age), 18);
unsafe.putObject(user, unsafe.objectFieldOffset(name), "android TV");

Object staticFieldBase = unsafe.staticFieldBase(id);
System.out.println("staticBase:" + staticFieldBase);

//获取静态变量id的偏移量

long staticOffset = unsafe.staticFieldOffset(id);
System.out.println("设置前的ID:" + unsafe.getObject(staticFieldBase, staticOffset));
//此处需要使用staticFieldBase返回的结果
unsafe.putObject(staticFieldBase, staticOffset, "ssss");
//获取静态变量的值
System.out.println("设置前的ID:" + unsafe.getObject(staticFieldBase, staticOffset));
//输出USER
System.out.println("输出USER:" + user.toString());


long data = 1000;
byte size = 1;
long memeryAddress = unsafe.allocateMemory(size);
unsafe.putAddress(memeryAddress, data);
long addrData = unsafe.getAddress(memeryAddress);
System.out.println("addrData:"+addrData);
}

/**
输出结果

staticBase:class self.unsafe.User
设置前的ID:USE_ID
设置前的ID:ssss
输出USER:User{name='android TV', age=18, id=ssss'}
addrData:1000

*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class User {

public User() {
System.out.println("user 构造方法调用");
}


private String name;
private int age;
private static String id = "USE_ID";

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 static String getId() {
return id;
}

public static void setId(String id) {
User.id = id;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", id=" + id + '\'' +
'}';
}
}

参考

-------------本文结束感谢您的阅读-------------

本文标题:java多线程-Unsafe

文章作者:小建儿

发布时间:2018年07月13日 - 10:07

最后更新:2018年07月19日 - 11:07

原始链接:http://yajian.github.io/java多线程-Unsafe/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。