文章目錄
- 簡介
- 獲取Unsafe的實體
- 使用Unsafe實體化一個類
- 修改私有欄位的值
- 拋出checked例外
- 使用堆外記憶體
- CompareAndSwap操作
- park/unpark
- 總結
- 擴展
簡介
Unsafe為我們提供了訪問底層的機制,這種機制僅供java核心類別庫使用,而不應該被普通用戶使用,但是,為了更好地了解java的生態體系,我們應該去學習它,去了解它,不求深入到底層的C/C++代碼,但求能了解它的基本功能,
獲取Unsafe的實體
可以看到Unsafe僅供java內部類使用
private Unsafe() {} // 私有構造
private static final Unsafe theUnsafe = new Unsafe(); // 私有的Unsafe物件屬性
查看Unsafe的原始碼我們會發現它提供了一個getUnsafe()的靜態方法
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
如果直接呼叫這個方法會拋出一個SecurityException例外,這是因為Unsafe僅供java內部類使用,外部類不應該使用它

我們可以用反射,上面我們知道它有一個屬性叫theUnsafe,我們直接通過反射拿到它即可,反射不理解可以參考Java反射.pptx
public static void main(String[] args) throws Exception {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
System.out.println(unsafe.getClass());
// 輸出class sun.misc.Unsafe
}
使用Unsafe實體化一個類
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()方法,我們可以修改任意私有欄位的值,
/**
* User類
*/
class User {
private int age;
public User(){
}
public int getAge(){
return age;
}
}
/**
* 原來的反射修改私有屬性的方法
*/
public static void main(String[] args) throws Exception {
Field f1 = User.class.getDeclaredField("age");
User user = User.class.getConstructor().newInstance();
f1.setAccessible(true);
f1.set(user,10);
}
// 輸出10
System.out.println(f1.getInt(user));
/**
* 使用Unsafe
*/
public static void main(String[] args) throws Exception {
User user = User.class.getConstructor().newInstance();
Field age = user.getClass().getDeclaredField("age");
unsafe.putInt(user,unsafe.objectFieldOffset(age),20);
// 輸出20
System.out.println(user.getAge());
}
一旦我們通過反射呼叫得到欄位age,我們就可以使用Unsafe將其值更改為任何其他int值
拋出checked例外
我們知道如果代碼拋出了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 void set(long i , int value){
unsafe.putInt(address+i*INT,value);
}
// 獲取指定索引處的元素
public int get(long i){
return unsafe.getInt(address+i*INT);
}
// 釋放堆外記憶體
public void freeMemory(){
unsafe.freeMemory(address);
}
}
在構造方法中呼叫allocateMemory()分配記憶體,在使用完成后呼叫freeMemory()釋放記憶體
public static void main(String[] args) throws Exception {
OffHeapArray off = new OffHeapArray(4);
off.set(1,2);
off.set(2,3);
off.set(3,4);
off.set(2,5);
off.set(0,6);
int sum = 0;
for (long i = 0; i < off.getSize(); i++) {
sum += off.get(i);
}
// 輸出17
System.out.println(sum);
off.freeMemory();
}
最后,一定要記得呼叫freeMemory()將記憶體釋放回作業系統
CompareAndSwap操作
也就是CAS
JUC下面大量使用了CAS操作,它們的底層是呼叫的Unsafe的CompareAndSwapXXX()方法,這種方式廣泛運用于無鎖演算法,與java中標準的悲觀鎖機制相比,它可以利用CAS處理器指令提供極大的加速,
比如,我們可以基于Unsafe的compareAndSwapInt()方法構建執行緒安全的計數器,
class Counter{
private volatile int count = 0;
// 記錄count欄位的偏移量 用Unsafe獲取
private static long offset;
// 獲取Unsafe物件
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
// 設定count偏移量,為了能讓Unsafe對其操作
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;
}
}
在increment()方法中,我們通過呼叫Unsafe的compareAndSwapInt()方法來嘗試更新之前獲取到的count的值,如果它沒有被其它執行緒更新過(這里就是偏移量的作用,如果偏移量發生改變就說明此執行緒在改count的時候被其他執行緒修改過,所以會失敗,就會重新獲取count的值,重新判斷),則更新成功,否則不斷重試直到成功為止,
public static void main(String[] args) throws Exception{
Counter counter = new Counter();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i1 = 0; i1 < 1000; i1++) {
counter.increment();
}
}
}).start();
}
// 為了防止main執行緒提前結束
Thread.sleep(2000);
// 輸出100000
System.out.println(counter.getCount());
}
park/unpark
JVM在背景關系切換的時候使用了Unsafe中的兩個方法park()和unpark(),
-
當一個執行緒正在等待某個操作時,JVM呼叫Unsafe的park()方法來阻塞此執行緒,
-
當阻塞中的執行緒需要再次運行時,JVM呼叫Unsafe的unpark()方法來喚醒此執行緒,
總結
使用Unsafe幾乎可以操作一切:
(1)實體化一個類;
(2)修改私有欄位的值;
(3)拋出checked例外;
(4)使用堆外記憶體;
(5)CAS操作;
(6)阻塞/喚醒執行緒;
擴展
論實體化一個類的方式?
(1)通過構造方法實體化一個類;
(2)通過Class實體化一個類;
(3)通過反射實體化一個類;
(4)通過克隆實體化一個類;
(5)通過反序列化實體化一個類;
(6)通過Unsafe實體化一個類;
public class Demo8 {
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{
/**
* 通過構造方法
*/
Student s1 = new Student();
/**
* 通過Class實體化一個類
*/
Student s2 = Student.class.newInstance();
/**
* 通過反射實體化一個類
*/
Student s3 = Student.class.getConstructor().newInstance();
/**
* 通過克隆實體化一個類
*/
Student s4 = (Student) s3.clone();
/**
* 通過反序列化實體化一個類
*/
Student s5 = unserialize(s4);
/**
* 通過Unsafe實體化一個類
*/
Student s6 = (Student) unsafe.allocateInstance(Student.class);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println(s5);
System.out.println(s6);
/*
Student@6d21714c
Student@108c4c35
Student@4ccabbaa
Student@5f4da5c3
Student@4bf558aa
Student@2d38eb89
*/
}
public static Student unserialize(Student student) throws Exception{
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("D://a.txt"));
output.writeObject(student);
output.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("D://a.txt"));
Student student1 = (Student)input.readObject();
input.close();
return student1;
}
}
class Student implements Cloneable,Serializable {
private int age;
public Student(){
this.age = 10;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/354625.html
標籤:java
