(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();
}
}
}
複製代碼
歡迎關注個人公衆號「彤哥讀源碼」,查看更多源碼系列文章, 與彤哥一塊兒暢遊源碼的海洋。