Java不能操作内存?Unsafe了解一下

种地  金牌会员 | 2023-8-10 09:32:52 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 960|帖子 960|积分 2880

前言

C++可以动态的分类内存(但是得主动释放内存,避免内存泄漏),而java并不能这样,java的内存分配和垃圾回收统一由JVM管理,是不是java就不能操作内存呢?当然有其他办法可以操作内存,接下来有请Unsafe出场,我们一起看看Unsafe是如何花式操作内存的。
Unsafe介绍

Unsafe见名知意,不安全的意思,因为通过这个类可以绕过JVM的管理直接去操作内存,如果操作不当或者使用完成后没有及时释放的话,这部分的内存不会被回收,久而久之,这种没有被释放的内存会越来越多造成内存泄漏。所以这是一个比较不安全的操作,一般不建议直接使用,毕竟这种问题导致的线上问题很难查出,另外通常的解决办法就是重启系统了。
虽然Unsafe是不安全的操作,但可以根据实际情况合理使用可以达到更好的效果,比如像java NIO里就有通过这种方式创建堆外存。Unsafe常用的操作包括:分配内存释放内存、实例化及对象操作、数组操作、CAS、线程同步等。
Unsafe实例对象的获取

Unsafe不能直接获取到,像下面这样使用会直接抛出安全检查异常。
  1. public static void main(String[] args) throws Exception {
  2.     Unsafe unsafe = Unsafe.getUnsafe();
  3. }
复制代码
运行结果就这样:
  1. Exception in thread "main" java.lang.SecurityException: Unsafe
  2.         at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
  3.         at com.star95.study.UnsafeTest.main(UnsafeTest.java:21)
复制代码
我们来看看sun.misc.Unsafe.getUnsafe这个方法的源码:
  1. public static Unsafe getUnsafe() {
  2.     Class var0 = Reflection.getCallerClass();
  3.     if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
  4.         throw new SecurityException("Unsafe");
  5.     } else {
  6.         return theUnsafe;
  7.     }
  8. }
复制代码
这里会判断这个类的加载器是否是启用类加载器Bootstrap ClassLoader,如果不是启动类加载器加载的则抛异常。
Bootstrap ClassLoader这个类加载器主要加载java核心类库,比如rt.jar这类包的,java采用的是双亲委托方式加载类,如果父加载器已加载了某个类,则子加载器不再加载,采用这样的方式进一步加强安全性。关于启动类加载器Bootstrap ClassLoader、扩展类加载器Extension Classloader、应用程序类加载器Application Classloader这里不再介绍,感兴趣的可自行搜索。
以下介绍3种方法来获取Unsafe。
1、修改启动类加载器的搜索路径
  1. public class UnsafeUtil {
  2.     private UnsafeUtil() {}
  3.     public static Unsafe getUnsafe() {
  4.         System.out.println("get getUnsafe...");
  5.         return Unsafe.getUnsafe();
  6.     }
  7. }
复制代码
就这一个类,打成一个jar包。

把这个jar包的路径放到jvm启动参数里-Xbootclasspath/a:D:\work\projects\test\unsafe\target\unsafe-1.0-SNAPSHOT.jar,然后调用即可获取到Unsafe。

关于jvm参数-Xbootclasspath说明:
  1. -Xbootclasspath:新jar路径(Windows用;分隔,linux用:分隔),这种相当于覆盖了java默认的核心类搜索路径,包括核心类库例如rt.jar,这种基本不用。
  2. -Xbootclasspath/a:新jar路径(Windows用;分隔,linux用:分隔),这种是把新jar路径追加到已有的classpath后,相当于扩大了核心类的范围,这个常用。
  3. -Xbootclasspath/p:新jar路径(Windows用;分隔,linux用:分隔),这种是把新jar路径追加到已有的classpath前,也相当于扩大了核心类的范围,但是放到核心类前可能会引起冲突,这个不常用。
复制代码
2、利用反射使用构造方法创建实例

Unsafe有一个私有的无参构造方法,利用反射使用构造方法也可以创建Unsafe实例。
  1. Constructor constructor = Unsafe.class.getDeclaredConstructors()[0];
  2. constructor.setAccessible(true);
  3. Unsafe unsafe = (Unsafe)constructor.newInstance();
  4. System.out.println(unsafe);
复制代码
3、利用反射获取Unsafe属性创建实例

Unsafe类里有一个属性private static final Unsafe theUnsafe;利用反射获取到这个属性然后对null这个对象获取属性值就会触发类静态块儿的执行,从而达到实例化的目的。
  1. private static Unsafe getUnsafe() {
  2.     try {
  3.         Field field = Unsafe.class.getDeclaredField("theUnsafe");
  4.         field.setAccessible(true);
  5.         return (Unsafe)field.get(null);
  6.     } catch (Exception e) {
  7.         e.printStackTrace();
  8.     }
  9.     return null;
  10. }
复制代码
以上3种方法虽然都可以获取到Unsafe的实例,但第三种更常用一些。
Unsafe常用操作

Unsafe类里大概有100多个方法,按用途主要分为以下几大类,分别介绍。
Unsafe操作内存

内存操作主要包括内存分配、扩展内存、设置内存值、释放内存等,常用的方法介绍如下。
  1. //分配指定大小的内存
  2. public long allocateMemory(long bytes)
  3. //根据给定的内存地址address调整内存大小
  4. public long reallocateMemory(long address, long bytes)
  5. //设置内存值
  6. public void setMemory(Object o, long offset, long bytes, byte value)
  7. public void setMemory(long address, long bytes, byte value)
  8. //内存复制,支持两种地址模式
  9. public void copyMemory(Object srcBase, long srcOffset,  Object destBase, long destOffset, long bytes)
  10. //释放allocateMemory和reallocateMemory申请的内存
  11. public native void freeMemory(long address)
复制代码
举个栗子:
  1. public void test() throws Exception {
  2.         Unsafe unsafe = getUnsafe();
  3.         long address = unsafe.allocateMemory(8);
  4.         System.out.println("allocate memory with 8 bytes, address=" + address);
  5.         long data = 13579L;
  6.         unsafe.putLong(address, data);
  7.         System.out.println("direct put data to address, data=" + data);
  8.         System.out.println("get address data=" + unsafe.getLong(address));
  9.         long address1 = unsafe.allocateMemory(8);
  10.         System.out.println("allocate memory with 8 bytes, address1=" + address);
  11.         unsafe.copyMemory(address, address1, 8);
  12.         System.out.println("copy memory with 8 bytes to address1=" + address1);
  13.         System.out.println("get address1 data=" + unsafe.getLong(address1));
  14.         unsafe.reallocateMemory(address1, 16);
  15.         unsafe.setMemory(address1, 16, (byte)20);
  16.         System.out.println("after setMemory address1=" + unsafe.getByte(address1));
  17.         unsafe.freeMemory(address1);
  18.         unsafe.freeMemory(address);
  19.         System.out.println("free memory over");
  20.         long[] l1 = new long[] {11, 22, 33, 44};
  21.         long[] l2 = new long[4];
  22.         long offset = unsafe.arrayBaseOffset(long[].class);
  23.         unsafe.copyMemory(l1, offset, l2, offset, 32);
  24.         System.out.println("l2=" + Arrays.toString(l2));
  25.     }
复制代码
输出结果:
  1. allocate memory with 8 bytes, address=510790256
  2. direct put data to address, data=13579
  3. get address data=13579
  4. allocate memory with 8 bytes, address1=510790256
  5. copy memory with 8 bytes to address1=510788736
  6. get address1 data=13579
  7. after setMemory address1=20
  8. free memory over
  9. l2=[11, 22, 33, 44]
复制代码
Unsafe操作类、对象及变量

下面介绍关于类、对象及变量相关的一些操作,还有一些其他的方法没有一一列出,大家可以自行研究。
  1. //实例化对象,不调构造方法
  2. Object allocateInstance(Class<?> cls)
  3. //字段在内存中的地址相对于实例对象内存地址的偏移量
  4. public long objectFieldOffset(Field f)
  5. //字段在内存中的地址相对于class内存地址的偏移量
  6. public long objectFieldOffset(Class<?> c, String name)
  7. //静态字段在class对象中的偏移
  8. public long staticFieldOffset(Field f)
  9. //获得静态字段所对应类对象
  10. public Object staticFieldBase(Field f)
  11. //获取对象中指定偏移量的int值,这里还有基本类型的其他其中,比如char,boolean,long等
  12. public native int getInt(Object o, long offset);
  13. //将int值放入指定对象指定偏移量的位置,这里还有基本类型的其他其中,比如char,boolean,long等
  14. public native void putInt(Object o, long offset, int x);
  15. //获取obj对象指定offset的属性对象
  16. public native Object getObject(Object obj, long offset);
  17. //将newObj对象放入指定obj对象指定offset偏移量的位置
  18. public native void putObject(Object obj, long offset, Object newObj);
复制代码
实战一下吧
  1. class Cat {
  2.     private String name;
  3.     private long speed;
  4.     public Cat(String name, long speed) {
  5.         this.name = name;
  6.         this.speed = speed;
  7.     }
  8.     public Cat() {
  9.         System.out.println("constructor...");
  10.     }
  11.     static {
  12.         System.out.println("static...");
  13.     }
  14.     @Override
  15.     public String toString() {
  16.         return "Cat{" + "name='" + name + '\'' + ", speed=" + speed + '}';
  17.     }
  18. }
  19. class Foo {
  20.     private int age;
  21.     private Cat cat;
  22.     private static String defaultString = "default........";
  23.     public int getAge() {
  24.         return age;
  25.     }
  26.     public void setAge(int age) {
  27.         this.age = age;
  28.     }
  29.     public Cat getCat() {
  30.         return cat;
  31.     }
  32.     public void setCat(Cat cat) {
  33.         this.cat = cat;
  34.     }
  35. }
  36. //测试方法
  37. public void test1() throws Exception {
  38.     // 使用allocateInstance方法创建一个Cat实例,这里不会调用构造方法,但是静态块会执行,所以会输出"static..."
  39.     Cat cat = (Cat)unsafe.allocateInstance(Cat.class);
  40.     System.out.println("allocateInstance cat--->" + cat);
  41.     Foo f = new Foo();
  42.     f.setAge(13);
  43.     f.setCat(new Cat("ketty", 120));
  44.     long ageOffset = unsafe.objectFieldOffset(Foo.class.getDeclaredField("age"));
  45.     // 这个offset的属性是一个Cat对象
  46.     long catOffset = unsafe.objectFieldOffset(Foo.class.getDeclaredField("cat"));
  47.     // 获取静态属性的时候直接用Class对象,用实例对象的话会发生NPE异常
  48.     long defaultStringOffset = unsafe.staticFieldOffset(Foo.class.getDeclaredField("defaultString"));
  49.     System.out.println("get age=" + unsafe.getInt(f, ageOffset));
  50.     System.out.println("get cat=" + unsafe.getObject(f, catOffset));
  51.     System.out.println("get defaultString=" + unsafe.getObject(Foo.class, defaultStringOffset));
  52.     System.out.println("---------------------");
  53.     // 操作内存放入新值
  54.     unsafe.putInt(f, ageOffset, 100);
  55.     unsafe.putObject(f, catOffset, new Cat("hello", 333));
  56.     unsafe.putObject(f, defaultStringOffset, "new default string");
  57.     System.out.println("after put then get age=" + unsafe.getInt(f, ageOffset));
  58.     System.out.println("after put then get cat=" + unsafe.getObject(f, catOffset));
  59.     System.out.println("after put then get defaultString=" + unsafe.getObject(f, defaultStringOffset));
  60.     System.out.println("---------------------");
  61. }
复制代码
程序输出如下:
  1. static...
  2. allocateInstance cat--->Cat{name='null', speed=0}
  3. get age=13
  4. get cat=Cat{name='ketty', speed=120}
  5. get defaultString=default........
  6. ---------------------
  7. after put then get age=100
  8. after put then get cat=Cat{name='hello', speed=333}
  9. after put then get defaultString=new default string
  10. ---------------------
复制代码
Unsafe操作数组

数组除了8种基本类型外,还包括Object数组。在Unsafe类里是通过静态块来获取这些数据。
  1. public void test2() {
  2.     // 获取8种基本类型和Object类型数组的基础偏移量,scale相关的可以理解每个类型对应的值所占的大小
  3.     // 通过输出信息我们可以看到基础偏移量都是16,scale除Object的是4外,基础数据类型的scale就是相应的字节大小
  4.     System.out.println("Unsafe.ARRAY_BOOLEAN_BASE_OFFSET=" + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET);
  5.     System.out.println("Unsafe.ARRAY_BOOLEAN_INDEX_SCALE=" + Unsafe.ARRAY_BOOLEAN_INDEX_SCALE);
  6.     System.out.println("Unsafe.ARRAY_BYTE_BASE_OFFSET=" + Unsafe.ARRAY_BYTE_BASE_OFFSET);
  7.     System.out.println("Unsafe.ARRAY_BYTE_INDEX_SCALE=" + Unsafe.ARRAY_BYTE_INDEX_SCALE);
  8.     System.out.println("Unsafe.ARRAY_SHORT_BASE_OFFSET=" + Unsafe.ARRAY_SHORT_BASE_OFFSET);
  9.     System.out.println("Unsafe.ARRAY_SHORT_INDEX_SCALE=" + Unsafe.ARRAY_SHORT_INDEX_SCALE);
  10.     System.out.println("Unsafe.ARRAY_CHAR_BASE_OFFSET=" + Unsafe.ARRAY_CHAR_BASE_OFFSET);
  11.     System.out.println("Unsafe.ARRAY_CHAR_INDEX_SCALE=" + Unsafe.ARRAY_CHAR_INDEX_SCALE);
  12.     System.out.println("Unsafe.ARRAY_INT_BASE_OFFSET=" + Unsafe.ARRAY_INT_BASE_OFFSET);
  13.     System.out.println("Unsafe.ARRAY_INT_INDEX_SCALE=" + Unsafe.ARRAY_INT_INDEX_SCALE);
  14.     System.out.println("Unsafe.ARRAY_LONG_BASE_OFFSET=" + Unsafe.ARRAY_LONG_BASE_OFFSET);
  15.     System.out.println("Unsafe.ARRAY_LONG_INDEX_SCALE=" + Unsafe.ARRAY_LONG_INDEX_SCALE);
  16.     System.out.println("Unsafe.ARRAY_FLOAT_BASE_OFFSET=" + Unsafe.ARRAY_FLOAT_BASE_OFFSET);
  17.     System.out.println("Unsafe.ARRAY_FLOAT_INDEX_SCALE=" + Unsafe.ARRAY_FLOAT_INDEX_SCALE);
  18.     System.out.println("Unsafe.ARRAY_DOUBLE_BASE_OFFSET=" + Unsafe.ARRAY_DOUBLE_BASE_OFFSET);
  19.     System.out.println("Unsafe.ARRAY_DOUBLE_INDEX_SCALE=" + Unsafe.ARRAY_DOUBLE_INDEX_SCALE);
  20.     System.out.println("Unsafe.ARRAY_OBJECT_BASE_OFFSET=" + Unsafe.ARRAY_OBJECT_BASE_OFFSET);
  21.     System.out.println("Unsafe.ARRAY_OBJECT_INDEX_SCALE=" + Unsafe.ARRAY_OBJECT_INDEX_SCALE);
  22.     System.out.println("------------------------------");
  23.     // 基本数据数组类型操作
  24.     int[] array = new int[] {11, 22, 33};
  25.     /*
  26.         改变最后一个元素的值,地址的算法就是:基础地址+偏移量,这个偏移量就是类型占用的大小*位置,Unsafe.ARRAY_INT_BASE_OFFSET + (array.length - 1) * Unsafe.ARRAY_INT_INDEX_SCALE
  27.      */
  28.     System.out.println("before put array[2]=" + array[2]);
  29.     unsafe.putInt(array, (long)Unsafe.ARRAY_INT_BASE_OFFSET + (array.length - 1) * Unsafe.ARRAY_INT_INDEX_SCALE,
  30.         100);
  31.     // 获取最后一个元素的值
  32.     System.out.println("after put array[2]=" + array[2]);
  33.     // 也可以这么获取,使用基础地址+偏移量的方式
  34.     System.out.println("after put array[2]=" + unsafe.getInt(array,
  35.         (long)Unsafe.ARRAY_INT_BASE_OFFSET + (array.length - 1) * Unsafe.ARRAY_INT_INDEX_SCALE));
  36.     System.out.println("-------------------");
  37.     // Object类型数组操作
  38.     Cat[] cats = {new Cat("cat1", 1), new Cat("cat2", 2), new Cat("cat3", 3)};
  39.     System.out.println("before put cats[2]=" + cats[2]);
  40.     unsafe.putObject(cats,
  41.         (long)Unsafe.ARRAY_OBJECT_BASE_OFFSET + (cats.length - 1) * Unsafe.ARRAY_OBJECT_INDEX_SCALE,
  42.         new Cat("newcat", 10000));
  43.     // 获取最后一个元素的值
  44.     System.out.println("after put cats[2]=" + cats[2]);
  45.     // 也可以这么获取,使用基础地址+偏移量的方式
  46.     System.out.println("after put cats[2]=" + unsafe.getObject(cats,
  47.         (long)Unsafe.ARRAY_OBJECT_BASE_OFFSET + (cats.length - 1) * Unsafe.ARRAY_OBJECT_INDEX_SCALE));
  48.     System.out.println("-------------------");
  49. }
复制代码
输出:
  1. Unsafe.ARRAY_BOOLEAN_BASE_OFFSET=16
  2. Unsafe.ARRAY_BOOLEAN_INDEX_SCALE=1
  3. Unsafe.ARRAY_BYTE_BASE_OFFSET=16
  4. Unsafe.ARRAY_BYTE_INDEX_SCALE=1
  5. Unsafe.ARRAY_SHORT_BASE_OFFSET=16
  6. Unsafe.ARRAY_SHORT_INDEX_SCALE=2
  7. Unsafe.ARRAY_CHAR_BASE_OFFSET=16
  8. Unsafe.ARRAY_CHAR_INDEX_SCALE=2
  9. Unsafe.ARRAY_INT_BASE_OFFSET=16
  10. Unsafe.ARRAY_INT_INDEX_SCALE=4
  11. Unsafe.ARRAY_LONG_BASE_OFFSET=16
  12. Unsafe.ARRAY_LONG_INDEX_SCALE=8
  13. Unsafe.ARRAY_FLOAT_BASE_OFFSET=16
  14. Unsafe.ARRAY_FLOAT_INDEX_SCALE=4
  15. Unsafe.ARRAY_DOUBLE_BASE_OFFSET=16
  16. Unsafe.ARRAY_DOUBLE_INDEX_SCALE=8
  17. Unsafe.ARRAY_OBJECT_BASE_OFFSET=16
  18. Unsafe.ARRAY_OBJECT_INDEX_SCALE=4
  19. ------------------------------
  20. before put array[2]=33
  21. after put array[2]=100
  22. after put array[2]=100
  23. -------------------
  24. static...
  25. before put cats[2]=Cat{name='cat3', speed=3}
  26. after put cats[2]=Cat{name='newcat', speed=10000}
  27. after put cats[2]=Cat{name='newcat', speed=10000}
  28. -------------------
复制代码
Tips:
如果操作的元素位置没有在数组范围内的话,put和get操作不会异常,都会成功,因为这是内存操作,使用的是基础地址+偏移量,但是并没有改变原始数组的大小,put后可以获取相应位置的内存数据,在没有put前调用get则获取的是数据类型的默认值。
CAS

比较并交换(Compare And Swap),在jvm里是一个原子操作,先获取内存的值,然后判断内存值和预期值是否相同,相同则更新为新值表示操作成功,不同则直接返回false,表明操作失败。java里的JUC包下很多队列或者锁都采用了这种实现方式。
  1. //每次都从主内存获取var1对象var2偏移量的long值
  2. public native long getLongVolatile(Object var1, long var2);
  3. //将var4值放入指定var1对象的var2偏移量位置,直接刷新到主内存
  4. public native void putLongVolatile(Object var1, long var2, long var4);
  5. // 比较并替换原值为新值,操作成功返回true否则false,var1是指定对象,var2是偏移量地址,var4是预期的原值,var5是要更新的新值
  6. public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
  7. // 自旋获取原值并增加数值,var1是指定对象,var2是偏移量地址,var4是要增加的值
  8. public final int getAndAddLong(Object var1, long var2, long var4) {
  9.     int var5;
  10.     do {
  11.         var5 = this.getLongVolatile(var1, var2);
  12.     } while(!this.compareAndSwapLong(var1, var2, var5, var5 + var4));
  13.     return var5;
  14. }
  15. // 自旋获取原值并设置新值,var1是指定对象,var2是偏移量地址,var4是要设置的新值
  16. public final int getAndSetLong(Object var1, long var2, long var4) {
  17.     int var5;
  18.     do {
  19.         var5 = this.getIntVolatile(var1, var2);
  20.     } while(!this.compareAndSwapLong(var1, var2, var5, var4));
  21.     return var5;
  22. }
  23. // 还有Object相关的cas操作这里没有列出
复制代码
举几个栗子
  1. public void test3() throws Exception {
  2.     Cat cat = new Cat("Kitty", 1000);
  3.     long speedOffset = unsafe.objectFieldOffset(Cat.class.getDeclaredField("speed"));
  4.     System.out.println("before putLongVolatile,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
  5.     // 设置speed的值为2000
  6.     unsafe.putLongVolatile(cat, speedOffset, 2000);
  7.     System.out.println("after putLongVolatile,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
  8.     // 到这里speed的值是2000,但是compareAndSwapLong里预期的值是3000,所以cas失败,返回false
  9.     System.out.println("compareAndSwapLong result:" + unsafe.compareAndSwapLong(cat, speedOffset, 3000, 4000));
  10.     // 到这里speed的值是2000,但是compareAndSwapLong里预期的值是2000,cas更新成功,返回true
  11.     System.out.println("compareAndSwapLong result:" + unsafe.compareAndSwapLong(cat, speedOffset, 2000, 4000));
  12.     // cas后speed的值就是4000了
  13.     System.out.println("after compareAndSwapLong,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
  14.     // getAndAddLong会返回原值4000,新值=原值+10
  15.     System.out.println("getAndAddLong:" + unsafe.getAndAddLong(cat, speedOffset, 10));
  16.     // getAndAddLong后speed新值是4010
  17.     System.out.println("after getAndAddLong,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
  18.     // getAndSetLong会返回原值4010,新值=要设置的新值1000
  19.     System.out.println("getAndSetLong:" + unsafe.getAndSetLong(cat, speedOffset, 1000));
  20.     // getAndSetLong后speed新值是1000
  21.     System.out.println("after getAndSetLong,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
  22. }
复制代码
输出结果:
  1. static...
  2. before putLongVolatile,getLongVolatile=1000
  3. after putLongVolatile,getLongVolatile=2000
  4. compareAndSwapLong result:false
  5. compareAndSwapLong result:true
  6. after compareAndSwapLong,getLongVolatile=4000
  7. getAndAddLong:4000
  8. after getAndAddLong,getLongVolatile=4010
  9. getAndSetLong:4010
  10. after getAndSetLong,getLongVolatile=1000
复制代码
线程调度及同步
  1. // 释放线程让其继续执行,多次调用只会生效一次,可以在park前调用
  2. public native void unpark(Object thread);
  3. // 阻塞线程,isAbsolute为true:表示绝对时间,time的单位是毫秒ms,false:表示相对时间,time的单位是纳秒级的时间
  4. public native void park(boolean isAbsolute, long time);
  5. //以下3个方法均标注过期了,建议使用其他同步方法
  6. // 获取var1的对象锁,没获取到则阻塞等待
  7. public native void monitorEnter(Object var1);
  8. // 尝试获取var1的对象锁,不阻塞,获取到则返回true,没获取到返回false
  9. public native boolean tryMonitorEnter(Object var1);
  10. // 释放var1对象锁
  11. public native void monitorExit(Object var1);
复制代码
我们先看看monitor同步相关的测试:
  1. public void test4() {
  2.     Object obj = new Object();
  3.     Thread t1 = new Thread(new Runnable() {
  4.         @Override
  5.         public void run() {
  6.             try {
  7.                 System.out.println(Thread.currentThread().getName() + " isrunning...");
  8.                 unsafe.monitorEnter(obj);
  9.                 System.out.println(Thread.currentThread().getName() + " got monitorEnter...");
  10.                 Thread.sleep(3000);
  11.                 System.out.println(Thread.currentThread().getName() + " business over...");
  12.             } catch (Exception e) {
  13.                 e.printStackTrace();
  14.             } finally {
  15.                 unsafe.monitorExit(obj);
  16.                 System.out.println(Thread.currentThread().getName() + " monitorExit...");
  17.             }
  18.         }
  19.     });
  20.     Thread t2 = new Thread(new Runnable() {
  21.         @Override
  22.         public void run() {
  23.             try {
  24.                 System.out.println(Thread.currentThread().getName() + " isrunning...");
  25.                 unsafe.monitorEnter(obj);
  26.                 System.out.println(Thread.currentThread().getName() + " got monitorEnter...");
  27.                 Thread.sleep(2000);
  28.                 System.out.println(Thread.currentThread().getName() + " business over...");
  29.             } catch (Exception e) {
  30.                 e.printStackTrace();
  31.             } finally {
  32.                 unsafe.monitorExit(obj);
  33.                 System.out.println(Thread.currentThread().getName() + " monitorExit...");
  34.             }
  35.         }
  36.     });
  37.     Thread t3 = new Thread(new Runnable() {
  38.         @Override
  39.         public void run() {
  40.             boolean flag = false;
  41.             try {
  42.                 System.out.println(Thread.currentThread().getName() + " isrunning...");
  43.                 flag = unsafe.tryMonitorEnter(obj);
  44.                 System.out.println(Thread.currentThread().getName() + " tryMonitorEnter:" + flag);
  45.             } catch (Exception e) {
  46.                 e.printStackTrace();
  47.             } finally {
  48.                 if (flag) {
  49.                     unsafe.monitorExit(obj);
  50.                     System.out.println(Thread.currentThread().getName() + " monitorExit...");
  51.                 }
  52.             }
  53.             System.out.println(Thread.currentThread().getName() + " over...");
  54.         }
  55.     });
  56.     t1.start();
  57.     t2.start();
  58.     t3.start();
  59. }
复制代码
可能的一种输出如下(线程是根据系统分配资源调度的,输出先后顺序会有多种),下面的这个输出我们可以看到先输出3个线程都启动了,Thread-2尝试获取锁失败就结束了,然后Thread-0竞争到了对象锁,等Thread-0线程运行完毕释放了锁,Thread-1才会获取到锁继续执行直到结束释放锁。
Tips:
monitor相关的方法已经加了@deprecated注解,官方已经不再建议使用,可以换成其他锁或者同步方式
  1. Thread-0 isrunning...
  2. Thread-2 isrunning...
  3. Thread-2 tryMonitorEnter:false
  4. Thread-2 over...
  5. Thread-1 isrunning...
  6. Thread-0 got monitorEnter...
  7. Thread-0 business over...
  8. Thread-0 monitorExit...
  9. Thread-1 got monitorEnter...
  10. Thread-1 business over...
  11. Thread-1 monitorExit...
复制代码
我们在来看看park、unpark相关的使用
  1. public void test5() throws Exception {
  2.     DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  3.     System.out
  4.         .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " is running...");
  5.     // 这里让当前线程阻塞6s,注意:如果park第一个参数是true的话,表示绝对时间,这个时间是毫秒级的,也就是系统时间,系统到这个绝对时间后才唤醒执行
  6.     unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(6));
  7.     System.out
  8.         .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " continue...");
  9.     // 这里让当前线程阻塞3s,注意:如果park第一个参数是false的话,这个是纳秒级别的时间,表示相对当前时间3s后继续唤醒执行
  10.     unsafe.park(false, TimeUnit.SECONDS.toNanos(3));
  11.     System.out
  12.         .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " continue...");
  13.     // 如果park的第一个参数是false,第二个值是0,则会一直等待,直到其他线程调用了unpark这个线程才会结束阻塞
  14.     // 一般像这种无限期等待的调了多少次park(false, 0)就要对于调同样次数的unpark才会完全解除阻塞
  15.     unsafe.park(false, 0);
  16. }
复制代码
输出:
  1. 2020-05-11 08:01:04 main is running...
  2. 2020-05-11 08:01:10 main continue...
  3. 2020-05-11 08:01:13 main continue...
复制代码
再看一个案例:
  1. public void test6() throws Exception {
  2.     DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  3.     Thread t1 = new Thread(new Runnable() {
  4.         @Override
  5.         public void run() {
  6.             try {
  7.                 System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
  8.                     + " is running...");
  9.                 // 这里让当前线程阻塞600s也就是10分钟,注意:如果park第一个参数是true的话,表示绝对时间,这个时间是毫秒级的,也就是系统时间,系统到这个绝对时间后才唤醒执行
  10.                 unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(600));
  11.                 System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
  12.                     + " continue...");
  13.             } catch (Exception e) {
  14.                 e.printStackTrace();
  15.             }
  16.         }
  17.     });
  18.     Thread t2 = new Thread(new Runnable() {
  19.         @Override
  20.         public void run() {
  21.             try {
  22.                 System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
  23.                     + " is running...");
  24.                 // 这里让当前线程阻塞600s也就是10分钟,注意:如果park第一个参数是true的话,表示绝对时间,这个时间是毫秒级的,也就是系统时间,系统到这个绝对时间后才唤醒执行
  25.                 unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(600));
  26.                 // unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(600));
  27.                 System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
  28.                     + " continue...");
  29.             } catch (Exception e) {
  30.                 e.printStackTrace();
  31.             }
  32.         }
  33.     });
  34.     t1.start();
  35.     t2.start();
  36.     // 主线程休眠2秒
  37.     Thread.sleep(2000);
  38.     System.out
  39.         .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " is running...");
  40.     // 这里调了unpark方法,参数就是t1线程,unsafe.unpark唤醒了t1线程,使得t1线程不用等到10分钟立马就可以执行
  41.     unsafe.unpark(t1);
  42.     // 下面连续调用了两次unpark t2线程,但是结果只释放了一次令牌,如果把t2线程的unsafe.park注释去掉,那么t2线程会一直等到park的时间到后被唤醒执行,
  43.     unsafe.unpark(t2);
  44.     unsafe.unpark(t2);
  45.     System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " over...");
  46. }
复制代码
输出如下:
  1. 2020-05-11 08:05:26 Thread-1 is running...
  2. 2020-05-11 08:05:26 Thread-0 is running...
  3. 2020-05-11 08:05:28 main is running...
  4. 2020-05-11 08:05:28 main over...
  5. 2020-05-11 08:05:28 Thread-1 continue...
  6. 2020-05-11 08:05:28 Thread-0 continue...
复制代码
unsafe的park和unpark在JUC并发包下使用的特别多,后续再介绍吧
内存屏障

这个我没有深入的了解,网上找了些资料看了看,没有具体的实践过,大家可以了解下。
  1. loadFence:保证在这个屏障之前的所有读操作都已经完成。
  2. storeFence:保证在这个屏障之前的所有写操作都已经完成。
  3. fullFence:保证在这个屏障之前的所有读写操作都已经完成。
复制代码
其他

类加载,类实例化相关的一些方法,还有其他的方法,这里不再一一说明,虽然不常用,但是了解其运行原理,或者去研究一下jvm的源码对自己都是一种提升。
再见

好了,就胡扯到这里吧,文章里的都是个人理解和实践,难免会有理解错误和实践错误的,请各位看官多多指正。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表