(1)Unsafe是什么?java
(2)Unsafe只有CAS的功能吗?git
(3)Unsafe为何是不安全的?算法
(4)怎么使用Unsafe?数组
本章是java并发包专题的第一章,可是第一篇写的却不是java并发包中类,而是java中的魔法类sun.misc.Unsafe。安全
Unsafe为咱们提供了访问底层的机制,这种机制仅供java核心类库使用,而不该该被普通用户使用。并发
可是,为了更好地了解java的生态体系,咱们应该去学习它,去了解它,不求深刻到底层的C/C++代码,但求能了解它的基本功能。ide
查看Unsafe的源码咱们会发现它提供了一个getUnsafe()的静态方法。学习
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
复制代码
可是,若是直接调用这个方法会抛出一个SecurityException异常,这是由于Unsafe仅供java内部类使用,外部类不该该使用它。测试
那么,咱们就没有方法了吗?this
固然不是,咱们有反射啊!查看源码,咱们发现它有一个属性叫theUnsafe,咱们直接经过反射拿到它便可。
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
}
}
复制代码
假如咱们有一个简单的类以下:
class User {
int age;
public User() {
this.age = 10;
}
}
复制代码
若是咱们经过构造方法实例化这个类,age属性将会返回10。
User user1 = new User();
// 打印10
System.out.println(user1.age);
复制代码
若是咱们调用Unsafe来实例化呢?
User user2 = (User) unsafe.allocateInstance(User.class);
// 打印0
System.out.println(user2.age);
复制代码
age将返回0,由于Unsafe.allocateInstance()
只会给对象分配内存,并不会调用构造方法,因此这里只会返回int类型的默认值0。
使用Unsafe的putXXX()方法,咱们能够修改任意私有字段的值。
public class UnsafeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
User user = new User();
Field age = user.getClass().getDeclaredField("age");
unsafe.putInt(user, unsafe.objectFieldOffset(age), 20);
// 打印20
System.out.println(user.getAge());
}
}
class User {
private int age;
public User() {
this.age = 10;
}
public int getAge() {
return age;
}
}
复制代码
一旦咱们经过反射调用获得字段age,咱们就可使用Unsafe将其值更改成任何其余int值。(固然,这里也能够经过反射直接修改)
咱们知道若是代码抛出了checked异常,要不就使用try...catch捕获它,要不就在方法签名上定义这个异常,可是,经过Unsafe咱们能够抛出一个checked异常,同时却不用捕获或在方法签名上定义它。
// 使用正常方式抛出IOException须要定义在方法签名上往外抛
public static void readFile() throws IOException {
throw new IOException();
}
// 使用Unsafe抛出异常不须要定义在方法签名上往外抛
public static void readFileUnsafe() {
unsafe.throwException(new IOException());
}
复制代码
若是进程在运行过程当中JVM上的内存不足了,会致使频繁的进行GC。理想状况下,咱们能够考虑使用堆外内存,这是一块不受JVM管理的内存。
使用Unsafe的allocateMemory()咱们能够直接在堆外分配内存,这可能很是有用,但咱们要记住,这个内存不受JVM管理,所以咱们要调用freeMemory()方法手动释放它。
假设咱们要在堆外建立一个巨大的int数组,咱们可使用allocateMemory()方法来实现:
class OffHeapArray {
// 一个int等于4个字节
private static final int INT = 4;
private long size;
private long address;
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 构造方法,分配内存
public OffHeapArray(long size) {
this.size = size;
// 参数字节数
address = unsafe.allocateMemory(size * INT);
}
// 获取指定索引处的元素
public int get(long i) {
return unsafe.getInt(address + i * INT);
}
// 设置指定索引处的元素
public void set(long i, int value) {
unsafe.putInt(address + i * INT, value);
}
// 元素个数
public long size() {
return size;
}
// 释放堆外内存
public void freeMemory() {
unsafe.freeMemory(address);
}
}
复制代码
在构造方法中调用allocateMemory()分配内存,在使用完成后调用freeMemory()释放内存。
使用方式以下:
OffHeapArray offHeapArray = new OffHeapArray(4);
offHeapArray.set(0, 1);
offHeapArray.set(1, 2);
offHeapArray.set(2, 3);
offHeapArray.set(3, 4);
offHeapArray.set(2, 5); // 在索引2的位置重复放入元素
int sum = 0;
for (int i = 0; i < offHeapArray.size(); i++) {
sum += offHeapArray.get(i);
}
// 打印12
System.out.println(sum);
offHeapArray.freeMemory();
复制代码
最后,必定要记得调用freeMemory()将内存释放回操做系统。
JUC下面大量使用了CAS操做,它们的底层是调用的Unsafe的CompareAndSwapXXX()方法。这种方式普遍运用于无锁算法,与java中标准的悲观锁机制相比,它能够利用CAS处理器指令提供极大的加速。
好比,咱们能够基于Unsafe的compareAndSwapInt()方法构建线程安全的计数器。
class Counter {
private volatile int count = 0;
private static long offset;
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public void increment() {
int before = count;
// 失败了就重试直到成功为止
while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) {
before = count;
}
}
public int getCount() {
return count;
}
}
复制代码
咱们定义了一个volatile的字段count,以便对它的修改全部线程均可见,并在类加载的时候获取count在类中的偏移地址。
在increment()方法中,咱们经过调用Unsafe的compareAndSwapInt()方法来尝试更新以前获取到的count的值,若是它没有被其它线程更新过,则更新成功,不然不断重试直到成功为止。
咱们能够经过使用多个线程来测试咱们的代码:
Counter counter = new Counter();
ExecutorService threadPool = Executors.newFixedThreadPool(100);
// 起100个线程,每一个线程自增10000次
IntStream.range(0, 100)
.forEach(i->threadPool.submit(()->IntStream.range(0, 10000)
.forEach(j->counter.increment())));
threadPool.shutdown();
Thread.sleep(2000);
// 打印1000000
System.out.println(counter.getCount());
复制代码
JVM在上下文切换的时候使用了Unsafe中的两个很是牛逼的方法park()和unpark()。
当一个线程正在等待某个操做时,JVM调用Unsafe的park()方法来阻塞此线程。
当阻塞中的线程须要再次运行时,JVM调用Unsafe的unpark()方法来唤醒此线程。
咱们以前在分析java中的集合时看到了大量的LockSupport.park()/unpark(),它们底层都是调用的Unsafe的这两个方法。
使用Unsafe几乎能够操做一切:
(1)实例化一个类;
(2)修改私有字段的值;
(3)抛出checked异常;
(4)使用堆外内存;
(5)CAS操做;
(6)阻塞/唤醒线程;
论实例化一个类的方式?
(1)经过构造方法实例化一个类;
(2)经过Class实例化一个类;
(3)经过反射实例化一个类;
(4)经过克隆实例化一个类;
(5)经过反序列化实例化一个类;
(6)经过Unsafe实例化一个类;
public class InstantialTest {
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
// 1. 构造方法
User user1 = new User();
// 2. Class,里面实际也是反射
User user2 = User.class.newInstance();
// 3. 反射
User user3 = User.class.getConstructor().newInstance();
// 4. 克隆
User user4 = (User) user1.clone();
// 5. 反序列化
User user5 = unserialize(user1);
// 6. Unsafe
User user6 = (User) unsafe.allocateInstance(User.class);
System.out.println(user1.age);
System.out.println(user2.age);
System.out.println(user3.age);
System.out.println(user4.age);
System.out.println(user5.age);
System.out.println(user6.age);
}
private static User unserialize(User user1) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://object.txt"));
oos.writeObject(user1);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://object.txt"));
// 反序列化
User user5 = (User) ois.readObject();
ois.close();
return user5;
}
static class User implements Cloneable, Serializable {
private int age;
public User() {
this.age = 10;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
}
复制代码
欢迎关注个人公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一块儿畅游源码的海洋。