当前位置:网站首页>【面试:并发篇34:Unsafe】
【面试:并发篇34:Unsafe】
2022-07-29 22:19:00 【I cream】
【面试:并发篇34:Unsafe】
00.前言
如果有任何问题请指出,感谢。
01.介绍
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得,因为Unsafe可以操控底层所以叫这个名字,并不是它线程不安全。
Unsafe源码
02.Unsafe CAS 操作
public class TestUnsafe {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
System.out.println(unsafe);
// 1. 获取域的偏移地址
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
Teacher t = new Teacher();
// 2. 执行 cas 操作
unsafe.compareAndSwapInt(t, idOffset, 0, 1);
unsafe.compareAndSwapObject(t, nameOffset, null, "张三");
// 3. 验证
System.out.println(t);
}
}
@Data
class Teacher {
volatile int id;
volatile String name;
}
结果
[email protected]
Teacher(id=1, name=张三)
解释
我们通过反射获取到Unsafe类,通过反射获取到成员变量的地址的偏移量 然后用Unsafe对象 unsafe直接cas改变Teacher对象t的值,最终打印发现确实改变了,我们通过更加底层的Unsafe 进行cas操作改变其他类 而不是通过原子更新器进行操作的。
03.通过Unsafe模拟原子整数类型
介绍
我们用Unsafe模拟的原子整数类型进行之前做过的取钱例子
代码
Account接口
interface Account {
// 获取余额
Integer getBalance();
// 取款
void withdraw(Integer amount);
/** * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作 * 如果初始余额为 10000 那么正确的结果应当是 0 */ static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
long start = System.nanoTime();
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance()
+ " cost: " + (end-start)/1000_000 + " ms");
}
}
获取Unsafe对象的工具类
public class UnsafeAccessor {
private static final Unsafe unsafe;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
public static Unsafe getUnsafe() {
return unsafe;
}
}
模拟取钱例子
@Slf4j(topic = "c.Test42")
public class Test42 {
public static void main(String[] args) {
Account.demo(new MyAtomicInteger(10000));
}
}
class MyAtomicInteger implements Account {
private volatile int value;
private static final long valueOffset;
private static final Unsafe UNSAFE;
static {
UNSAFE = UnsafeAccessor.getUnsafe();
try {
valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public int getValue() {
return value;
}
public void decrement(int amount) {
while(true) {
int prev = this.value;
int next = prev - amount;
if (UNSAFE.compareAndSwapInt(this, valueOffset, prev, next)) {
break;
}
}
}
public MyAtomicInteger(int value) {
this.value = value;
}
@Override
public Integer getBalance() {
return getValue();
}
@Override
public void withdraw(Integer amount) {
decrement(amount);
}
}
结果
0 cost: 102 ms
解释
我们可以看出用Unsafe可以模拟原子整数类型 并且取钱例子没有线程安全问题,事实上大家可以看看原子类型的源码 全部都是用Unsafe实现的。
边栏推荐
猜你喜欢
随机推荐
SQL 改写系列七:谓词移动
中科院TextMind(文心)安装及使用
JZ6 从尾到头打印链表
趣味隐写术与密码术(现代密码学教程)
聊聊阻容降压原理 和 实际使用的电路
【CVPR2022】A Unified Query-based Paradigm for Point Cloud Understanding
高等数学(第七版)同济大学 习题3-7 个人解答
JZ76 删除链表中重复的结点
支持keep alive长连接【转】
【luogu P8354】多边形(容斥)(NTT优化DP)
【技术规划】描绘未来第 4 部分:技术路线图
GBASE 8s 数据库复合索引
九、HikariCP源码分析之ConcurrentBag二
The implementation of the flood control project and safety construction project in the flood storage and detention areas in Luluze and Ningjinbo was launched
JZ23 链表中环的入口结点
GBASE 8s 如何并行执行update statistics
不要再用if-else!
DD5 进制转换
Spark读取多目录
模型评价指标汇总(持续更新)