1. <ul id="0c1fb"></ul>

      <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
      <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区

      RELATEED CONSULTING
      相關(guān)咨詢
      選擇下列產(chǎn)品馬上在線溝通
      服務(wù)時間:8:30-17:00
      你可能遇到了下面的問題
      關(guān)閉右側(cè)工具欄

      新聞中心

      這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
      死磕java魔法類之Unsafe解析

      問題

      (1)Unsafe是什么?

      創(chuàng)新互聯(lián)公司是專業(yè)的新密網(wǎng)站建設(shè)公司,新密接單;提供成都做網(wǎng)站、成都網(wǎng)站建設(shè),網(wǎng)頁設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行新密網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!

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

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

      (4)怎么使用Unsafe?

      簡介

      本章是java并發(fā)包專題的第一章,但是第一篇寫的卻不是java并發(fā)包中類,而是java中的魔法類sun.misc.Unsafe。

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

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

      獲取Unsafe的實(shí)例

      查看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異常,這是因?yàn)閁nsafe僅供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實(shí)例化一個類

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

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

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

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

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

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

      age將返回0,因?yàn)?code>Unsafe.allocateInstance()只會給對象分配內(nèi)存,并不會調(diào)用構(gòu)造方法,所以這里只會返回int類型的默認(rèn)值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)存

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

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

      假設(shè)我們要在堆外創(chuàng)建一個巨大的int數(shù)組,我們可以使用allocateMemory()方法來實(shí)現(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()方法。這種方式廣泛運(yùn)用于無鎖算法,與java中標(biāo)準(zhǔn)的悲觀鎖機(jī)制相比,它可以利用CAS處理器指令提供極大的加速。

      比如,我們可以基于Unsafe的compareAndSwapInt()方法構(gòu)建線程安全的計(jì)數(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/unpark

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

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

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

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

      總結(jié)

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

      (1)實(shí)例化一個類;

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

      (3)拋出checked異常;

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

      (5)CAS操作;

      (6)阻塞/喚醒線程;

      彩蛋

      論實(shí)例化一個類的方式?

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

      (2)通過Class實(shí)例化一個類;

      (3)通過反射實(shí)例化一個類;

      (4)通過克隆實(shí)例化一個類;

      (5)通過反序列化實(shí)例化一個類;

      (6)通過Unsafe實(shí)例化一個類;

      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,里面實(shí)際也是反射
              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();
              }
          }
      }

      歡迎關(guān)注我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

      死磕 java魔法類之Unsafe解析


      本文標(biāo)題:死磕java魔法類之Unsafe解析
      標(biāo)題來源:http://www.ef60e0e.cn/article/ppgpid.html
      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区
      1. <ul id="0c1fb"></ul>

        <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
        <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

        曲松县| 区。| 乌兰察布市| 桦甸市| 甘南县| 沅陵县| 通山县| 北川| 靖边县| 南投县| 隆德县| 墨玉县| 郸城县| 永平县| 陕西省| 高邮市| 任丘市| 景德镇市| 洪江市| 榕江县| 会东县| 贡山| 西畴县| 陇西县| 鄂托克旗| 永吉县| 繁昌县| 桐乡市| 邛崃市| 绥中县| 武宣县| 樟树市| 聂荣县| 甘孜| 江陵县| 乐亭县| 永胜县| 连山| 保定市| 澳门| 诸城市|