亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

java Unsafe詳細解析

 更新時間:2019年05月24日 08:58:46   作者:彤哥讀源碼  
Unsafe為我們提供了訪問底層的機制,這種機制僅供java核心類庫使用,而不應(yīng)該被普通用戶使用。但是,為了更好地了解java的生態(tài)體系,我們應(yīng)該去學(xué)習(xí)它,去了解它,不求深入到底層的C/C++代碼,但求能了解它的基本功能。下面小編來和大家一起學(xué)習(xí)

問題

(1)Unsafe是什么?

(2)Unsafe只有CAS的功能嗎?

(3)Unsafe為什么是不安全的?

(4)怎么使用Unsafe?

簡介

Unsafe為我們提供了訪問底層的機制,這種機制僅供java核心類庫使用,而不應(yīng)該被普通用戶使用。

但是,為了更好地了解java的生態(tài)體系,我們應(yīng)該去學(xué)習(xí)它,去了解它,不求深入到底層的C/C++代碼,但求能了解它的基本功能。

獲取Unsafe的實例

查看Unsafe的源碼我們會發(fā)現(xiàn)它提供了一個getUnsafe()的靜態(tài)方法。

@CallerSensitive
public static Unsafe getUnsafe() {
 Class var0 = Reflection.getCallerClass();
 if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
 throw new SecurityException("Unsafe");
 } else {
 return theUnsafe;
 }
}

但是,如果直接調(diào)用這個方法會拋出一個SecurityException異常,這是因為Unsafe僅供java內(nèi)部類使用,外部類不應(yīng)該使用它。

那么,我們就沒有方法了嗎?

當(dāng)然不是,我們有反射?。〔榭丛创a,我們發(fā)現(xiàn)它有一個屬性叫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);
 }
}

使用Unsafe實例化一個類

假如我們有一個簡單的類如下:

class User {
 int age;

 public User() {
 this.age = 10;
 }
}

如果我們通過構(gòu)造方法實例化這個類,age屬性將會返回10。

User user1 = new User();
// 打印10
System.out.println(user1.age);

如果我們調(diào)用Unsafe來實例化呢?

User user2 = (User) unsafe.allocateInstance(User.class);
// 打印0
System.out.println(user2.age);

age將返回0,因為Unsafe.allocateInstance()只會給對象分配內(nèi)存,并不會調(diào)用構(gòu)造方法,所以這里只會返回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;
 }
}

一旦我們通過反射調(diào)用得到字段age,我們就可以使用Unsafe將其值更改為任何其他int值。(當(dāng)然,這里也可以通過反射直接修改)

拋出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());
}

使用堆外內(nèi)存

如果進程在運行過程中JVM上的內(nèi)存不足了,會導(dǎo)致頻繁的進行GC。理想情況下,我們可以考慮使用堆外內(nèi)存,這是一塊不受JVM管理的內(nèi)存。

使用Unsafe的allocateMemory()我們可以直接在堆外分配內(nèi)存,這可能非常有用,但我們要記住,這個內(nèi)存不受JVM管理,因此我們要調(diào)用freeMemory()方法手動釋放它。

假設(shè)我們要在堆外創(chuàng)建一個巨大的int數(shù)組,我們可以使用allocateMemory()方法來實現(xiàn):

class OffHeapArray {
 // 一個int等于4個字節(jié)
 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();
 }
 }

 // 構(gòu)造方法,分配內(nèi)存
 public OffHeapArray(long size) {
 this.size = size;
 // 參數(shù)字節(jié)數(shù)
 address = unsafe.allocateMemory(size * INT);
 }
 
 // 獲取指定索引處的元素
 public int get(long i) {
 return unsafe.getInt(address + i * INT);
 }
 // 設(shè)置指定索引處的元素
 public void set(long i, int value) {
 unsafe.putInt(address + i * INT, value);
 }
 // 元素個數(shù)
 public long size() {
 return size;
 }
 // 釋放堆外內(nèi)存
 public void freeMemory() {
 unsafe.freeMemory(address);
 }
}

在構(gòu)造方法中調(diào)用allocateMemory()分配內(nèi)存,在使用完成后調(diào)用freeMemory()釋放內(nèi)存。

使用方式如下:

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的位置重復(fù)放入元素

int sum = 0;
for (int i = 0; i < offHeapArray.size(); i++) {
 sum += offHeapArray.get(i);
}
// 打印12
System.out.println(sum);

offHeapArray.freeMemory();

最后,一定要記得調(diào)用freeMemory()將內(nèi)存釋放回操作系統(tǒng)。

CompareAndSwap操作

JUC下面大量使用了CAS操作,它們的底層是調(diào)用的Unsafe的CompareAndSwapXXX()方法。這種方式廣泛運用于無鎖算法,與java中標(biāo)準(zhǔn)的悲觀鎖機制相比,它可以利用CAS處理器指令提供極大的加速。

比如,我們可以基于Unsafe的compareAndSwapInt()方法構(gòu)建線程安全的計數(shù)器。

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()方法中,我們通過調(diào)用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());

park/unparkJVM

在上下文切換的時候使用了Unsafe中的兩個非常牛逼的方法park()和unpark()。

當(dāng)一個線程正在等待某個操作時,JVM調(diào)用Unsafe的park()方法來阻塞此線程。

當(dāng)阻塞中的線程需要再次運行時,JVM調(diào)用Unsafe的unpark()方法來喚醒此線程。

我們之前在分析java中的集合時看到了大量的LockSupport.park()/unpark(),它們底層都是調(diào)用的Unsafe的這兩個方法。

總結(jié)

使用Unsafe幾乎可以操作一切:

(1)實例化一個類;

(2)修改私有字段的值;

(3)拋出checked異常;

(4)使用堆外內(nèi)存;

(5)CAS操作;

(6)阻塞/喚醒線程;

彩蛋

論實例化一個類的方式?

(1)通過構(gòu)造方法實例化一個類;

(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. 構(gòu)造方法
 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();
 }
 }
}

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 淺談JVM之類的加載鏈接和初始化

    淺談JVM之類的加載鏈接和初始化

    有了java class文件之后,為了讓class文件轉(zhuǎn)換成為JVM可以真正運行的結(jié)構(gòu),需要經(jīng)歷加載,鏈接和初始化的過程。這三個過程是怎么工作的呢?在本文中你將會找到答案。
    2021-06-06
  • Java使用entrySet方法獲取Map集合中的元素

    Java使用entrySet方法獲取Map集合中的元素

    這篇文章主要為大家詳細介紹了Java使用entrySet方法獲取Map集合中的元素,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • 使用java生成激活碼和密鑰的方法

    使用java生成激活碼和密鑰的方法

    本文主要介紹了java生成激活碼和密鑰的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • 手把手教你使用IDEA創(chuàng)建多模塊(maven)項目

    手把手教你使用IDEA創(chuàng)建多模塊(maven)項目

    這篇文章主要給大家介紹了關(guān)于如何使用IDEA創(chuàng)建多模塊(maven)項目的相關(guān)資料,文中通過圖文以及實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-07-07
  • Java編程基礎(chǔ)元素-運算符

    Java編程基礎(chǔ)元素-運算符

    這篇文章主要介紹了Java編程基礎(chǔ)元素-運算符,運算符就是在用變量或常量進行運算時,經(jīng)常需要用到的運算符,Java?提供了豐富的運算符,可分為算術(shù)運算符、關(guān)系運算符、邏輯運算符和位運算符,下面來看具體的內(nèi)容介紹吧
    2022-01-01
  • 繼承jpa?Repository?寫自定義方法查詢實例

    繼承jpa?Repository?寫自定義方法查詢實例

    這篇文章主要介紹了繼承jpa?Repository?寫自定義方法查詢實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • SpringBoot+layuimini實現(xiàn)左側(cè)菜單動態(tài)展示的示例代碼

    SpringBoot+layuimini實現(xiàn)左側(cè)菜單動態(tài)展示的示例代碼

    Layuimini是Layui的升級版,它是專業(yè)做后臺頁面的框架,而且是適合PC端和移動端,以下地址可以在PC端顯示,也可以在手機上顯示,只不過會做自適應(yīng),本文將給大家介紹了SpringBoot+layuimini實現(xiàn)左側(cè)菜單動態(tài)展示的方法,需要的朋友可以參考下
    2024-04-04
  • Springboot?application.yml配置文件拆分方式

    Springboot?application.yml配置文件拆分方式

    這篇文章主要介紹了Springboot?application.yml配置文件拆分方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • java?ReentrantLock并發(fā)鎖使用詳解

    java?ReentrantLock并發(fā)鎖使用詳解

    這篇文章主要為大家介紹了java?ReentrantLock并發(fā)鎖使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Java GZIP壓縮與解壓縮代碼實例

    Java GZIP壓縮與解壓縮代碼實例

    這篇文章主要介紹了Java GZIP壓縮與解壓縮代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01

最新評論