Java并发编程基础-Unsafe
2024-08-30 12:15:59
- 前言:
Unsafe是Java中一个底层类,包含了很多基础的操作,比如数组操作、对象操作、内存操作、CAS操作、线程(park)操作、栅栏(Fence)操作,JUC包、一些三方框架都使用Unsafe类来保证并发安全。 - 介绍:
- 获取Unsafe对象
Unsafe构造方法为私有,虽然提供了一个getUnsafe静态方法,但会判断加载这个类的加载器是否为null,即判断加载器是否为Bootstrap ClassLoader。用户创建的类默认都是由App ClassLoader进行加载,因此自己编写的代码不使用特殊方式是无法获取到Unsafe实例,观察源码得知,可以使用反射获取theUnsafe属性,从而获得实例,代码示例如下:Unsafe unsafe = null;
//get unsafe object
Field getUnsafe = null;
try {
getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
getUnsafe.setAccessible(true);
unsafe = (Unsafe) getUnsafe.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} - 基本操作
- 数组操作
可以获取数组的在内容中的基本偏移量(arrayBaseOffset),获取数组内元素的间隔(比例),根据数组对象和偏移量获取元素值(getObject),设置数组元素值(putObject),示例如下。String[] strings = new String[]{"1","2","3"};
long i = unsafe.arrayBaseOffset(String[].class);
System.out.println("string[] base offset is :"+i);
//every index scale
long scale = unsafe.arrayIndexScale(String[].class);
System.out.println("string[] index scale is "+scale);
//print first string in strings[]
System.out.println("first element is :"+unsafe.getObject(strings, i));
//set 100 to first string
unsafe.putObject(strings,i+scale*0,"100");
//print first string in strings[] again
System.out.println("after set ,first element is :"+unsafe.getObject(strings, i+scale*0)); - 对象操作
可以通过类的class对象创建类对象(allocateInstance),获取对象属性的偏移量(objectFieldOffset),通过偏移量设置对象的值(putObject),示例如下。public class User {
private String username; private int age; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} }try {
User user = (User) unsafe.allocateInstance(User.class);
//simple set username
user.setUsername("123");
System.out.println("simple set,allocate Intstance user name:"+user.getUsername());
Field usernameField = User.class.getDeclaredField("username");
long objectFieldOffset = unsafe.objectFieldOffset(usernameField);
//use unsafe set
unsafe.putObject(user,objectFieldOffset,"456");
System.out.println("use unsafe,allocate Intstance user name:"+user.getUsername());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} - 内存操作
可以在Java内存区域中分配内存(allocateMemory),设置内存(setMemory,用于初始化),在指定的内存位置中设置值(putInt\putBoolean\putDouble等基本类型),示例如下。//分配一个8byte的内存
long address = unsafe.allocateMemory(8L);
//初始化内存填充0
unsafe.setMemory(address,8L,(byte) 0);
//测试输出
System.out.println("add byte to memory:"+unsafe.getInt(address));
//设置0-3 4个byte为0x7fffffff
unsafe.putInt(address,0x7fffffff);
//设置4-7 4个byte为0x80000000
unsafe.putInt(address+4,0x80000000);
//int占用4byte
System.out.println("add byte to memory:"+unsafe.getInt(address));
System.out.println("add byte to memory:"+unsafe.getInt(address+4)); - 常量获取
可以获取地址大小(addressSize),页大小(pageSize),基本类型数组的偏移量(Unsafe.ARRAY_INT_BASE_OFFSET\Unsafe.ARRAY_BOOLEAN_BASE_OFFSET等)、基本类型数组内元素的间隔(Unsafe.ARRAY_INT_INDEX_SCALE\Unsafe.ARRAY_BOOLEAN_INDEX_SCALE等),示例如下。
//get os address size
System.out.println("address size is :"+unsafe.addressSize());
//get os page size
System.out.println("page size is :"+unsafe.pageSize());
//int array base offset
System.out.println("unsafe array int base offset:"+Unsafe.ARRAY_INT_BASE_OFFSET); - 线程许可
许可线程通过(park),或者让线程等待许可(unpark),示例如下。Thread parkThread = new Thread(new Runnable() {
@Override
public void run() {
long startTime = System.currentTimeMillis();
//纳秒,相对时间park
unsafe.park(false,3000000000L);
//毫秒,绝对时间park
//unsafe.park(true,System.currentTimeMillis()+3000); System.out.println("main thread end,cost :"+(System.currentTimeMillis()-startTime)+"ms");
}
});
parkThread.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//注释掉下一行后,线程3秒数后进行输出,否则在1秒后输出
unsafe.unpark(parkThread); - CAS操作
Compare And Swap(比较并交换),当需要改变的值为期望的值时,那么就替换它为新的值,是原子(不可在分割)的操作。很多并发框架底层都用到了CAS操作,CAS操作优势是无锁,可以减少线程切换耗费的时间,但CAS经常失败运行容易引起性能问题,也存在ABA问题。在Unsafe中包含compareAndSwapObject、compareAndSwapInt、compareAndSwapLong三个方法,compareAndSwapInt的简单示例如下。//参考 介绍-基本操作-对象操作中的User定义
User user = new User();
user.setAge(1);
try {
Field ageField = user.getClass().getDeclaredField("age");
long l = unsafe.objectFieldOffset(ageField);
ageField.setAccessible(true);
//比较并交换,比如age的值如果是所期望的值1,那么就替换为2,否则不做处理
unsafe.compareAndSwapInt(user,l,1,2);
System.out.println("user age is :" + user.getAge()); } catch (NoSuchFieldException e) {
e.printStackTrace();
} - 内存栅栏
用于防止指令重排序,包含fullFence,loadFence,StoreFence三个方法。现代的CPU运行速度很快,很多指令重排序的例子已经无法得到想要的效果,因此借用stackoverflow上的一段伪代码示例。
// CPU 0:
void shutDownWithFailure(void)
{
failure = 1; // must use SOB as this is owned by CPU 1
SFENCE // next instruction will execute after all SOBs are processed
shutdown = 1; // can execute immediately as it is owned be CPU 0
}
// CPU1:
void workLoop(void)
{
while (shutdown == 0) { ... }
LFENCE // next instruction will execute after all LOBs are processed
if (failure) { ...}
}
- 数组操作
- 获取Unsafe对象
PS:
- 在Java新版本开发的过程中,曾经传出Oracle要移除掉Unsafe类,引起了很大的恐慌,但在Java9发布时,发现jdk.internal.misc包路径出现了Unsafe类,不仅开放使用而且还增加了大量的注释方便理解,说明Java在开源的道路上依然在前进。
- 对于getXXVolatile的理解:如果一个Java变量被Volatile修饰,使用此方法则可以获得内存内最新的值,而不是线程缓存;如果普通的Java变量使用此方法,那么和使用getXX()方法效果一样,可能获取到的事线程缓存。
参考书籍及网址:
- 《深入理解Java虚拟机》
- https://stackoverflow.com/questions/23603304/java-8-unsafe-xxxfence-instructions
PS:研究基于MAC+Idea+JDK1.8 64位
最新文章
- IEEE/ACM ASONAM 2014 Industry Track Call for Papers
- Windows Store App Image开发示例
- linux shell
- Python 之匿名函数和偏函数
- Java Socket编程(转)
- RobotFrameWork http/https oauth接口测试 (一)
- DBA_在Linux上安装Oracle Database11g数据库(案例)
- 云服务器 ECS Linux 修改编码格式
- H5页面请求跨域问题
- Ajax 技术原理(转)
- JSP的学习(1)——基本知识与底层原理
- web程序员标准环境之DreamWeaver【推荐】
- WebGL 3D 电信机架实战之数据绑定
- JS-DOM ~ 03. 子节点的操作、style.样式和属性、dom元素的创建方法及操作、14个例题、主要是利用js直接控制html属性
- SQL Server 2014 HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING 等待
- java并发包分析之———volitale
- Codeforces Round #553 (Div. 2) D题
- Confluence 6 系统运行信息中的 JVM 内存使用情况
- python爬虫在解析不带引号的json报错的问题解决方案
- 微信中关闭网页输入内容时的安全提示 [干掉 “防盗号或诈骗,请不要输入QQ密码”]