从字面意思就可以看出, 这些接口在java世界, 是不安全的, 都是一些native (jni) 接口, 会直接修改内存中的值, 一旦指针
弄错了, 结果是令人崩溃的.
本文只介绍几个常用的接口.
compareAndSwapInt
原型
/**
* Atomically update Java variable to x if it is currently
* holding expected.
* @return true if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
接口含义:
首先找出Object o
在内存中的位置p
, 然后偏移offset
个字节, 设p+offset
处的这个int值为y
,
如果y == expected
, 则执行赋值操作y = x
, 返回true
如果y != expected
, 则不执行赋值操作, 返回false
使用例子
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeExample {
private final static Logger logger = LogManager.getLogger(UnsafeExample.class);
static Unsafe U;
int x = 2;
static {
Field f = null;
try {
/* 我所使用的jdk8上是theUnsafe */
f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
U = (Unsafe) f.get(null);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Throwable{
long offset = U.objectFieldOffset(UnsafeExample.class.getDeclaredField("x"));
UnsafeExample example = new UnsafeExample();
boolean ret;
//如果x==2, 则执行x=3, 返回true
ret = U.compareAndSwapInt(example, offset, 2, 3);
logger.info("offset: {}, ret: {}, x: {}", offset, ret, example.x);
//如果x==4, 则执行x=5. 实际上x==3, 不成立, 赋值失败, 返回false
ret = U.compareAndSwapInt(example, offset, 4, 5);
logger.info("offset: {}, ret: {}, x: {}", offset, ret, example.x);
}
}
打印
2019-02-07 17:47:47.859 INFO main [UnsafeExample.java:34] offset: 12, ret: true, x: 3
2019-02-07 17:47:47.859 INFO main [UnsafeExample.java:36] offset: 12, ret: false, x: 3
compareAndSwapObject compareAndSwapLong
与compareAndSwapInt类似, 不多说了
compareAndSwap底层原理
compareAndSwap简称CAS, 以Intel CPU为例, 它有个指令叫lock
, 它可禁止不同的处理器同时操作CPU之间的共享内存.
以下是汇编举例
lock cmpxchg dword ptr [edx], ecx
解释
dword ptr
表示32bit指针
[edx]
为寄存器间接寻址
整句话的含义是:
将eax
中的值与[edx]
中的值比较,
如果相同, 则将ecx
中的值赋给[edx]
, 并且ZF
置1
如果不同, 则将[edx]
中的值赋给eax
. 并且ZF
清0
整个操作是原子的
getAndAddInt
原型 public final int getAndAddInt(Object o, long offset, int delta)
与compareAndSwapInt相似, 将对象偏移offset
地址的值加上delta
, 源代码如下
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
getAndAddLong也是类似的.
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, v + delta));
return v;
}
loadFence/storeFence
我也不懂这两个接口如何使用. 从底层的C++实现来看, 它影响的是对变量的读/写操作.
比如线程1
修改变量X
, 然后线程2
读取变量X
的值, 那么线程2
读到的到底是新值, 还是旧值?
有点类似volatile
的作用
put/get操作
例如putByte, putShort等
将对象偏移offset
后的地址的值修改为另一个值. 以修改数组举例
public class PutExample {
private final static Logger logger = LogManager.getLogger(PutExample.class);
static Unsafe U;
static {
Field f = null;
try {
f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
U = (Unsafe) f.get(null);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Throwable {
int offset = U.arrayBaseOffset(byte[].class);
final byte[] bytes = {1, 2, 3};
logger.info("offset: {}, src: {}", offset, bytes);
U.putByte(bytes, offset + 1L, (byte) 10);
logger.info("modified: {}", bytes);
}
}
执行结果为
2019-02-07 20:15:01.719 INFO main [PutExample.java:27] offset: 16, src: [1, 2, 3]
2019-02-07 20:15:01.719 INFO main [PutExample.java:29] modified: [1, 10, 3]
参考
JDK8 https://download.java.net/openjdk/jdk8/
Intel指令集 http://jsimlo.sk/docs/cpu/index.php/cmpxchg.html
http://jsimlo.sk/docs/cpu/index.php/lock.html