在 JUC 篇章中我们去了解了 JUC 相关的包结构和相关工具类锁等相关的概念,JUC 进阶篇章将深入去讲解相关的Java并发涉及的相关知识,以及涉及到的相关引用。
简介
Java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作, 为我们提供了访问底层的机制, 这种机制仅供Java 核心类库使用, 而不应该被普通用户使用。
所在包
package sun.misc;
Unsafe 的实例
查看Unsafe 的源码我们会发现它提供了一个 getUnsafe() 的静态方法
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (var0.getClassLoader() != null) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
直接调用这个方法会报一个SecurityException 异常, 这是因为Unsafe 仅给java 内部类使用,外部类不应该使用它。
但是Java 后门 反射可以达到我们使用此方法的目的
Unsafe 类中有个常量属性 theUnsafe
private static final Unsafe theUnsafe;
通过反射机制得到 unsafe 对象
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 Student{
int age ;
Student(){
this.age = 10 ;
}
}
正常情况下我们通过构造方法实例化这个类, age 属性返回是 10
package com.suning.test;
/**
* @Author wangli
* @Descrintion:
* @Date : Created in 11:47 2019/5/7
* @
*/
public class Student {
int age ;
Student(){
this.age = 10 ;
}
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.age);
}
}
如果我们使用 Unsafe来进行实例化 结果会是什么呢
package com.suning.test;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @Author wangli
* @Descrintion:
* @Date : Created in 11:47 2019/5/7
* @
*/
public class Student {
int age ;
Student(){
this.age = 10 ;
}
public static void main(String[] args)throws Exception {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
Student user = (Student)unsafe.allocateInstance(Student.class);
System.out.println(user.age);
}
}
根据结果我们看到输出的 0 ,这是因为 Unsafe.allocateInstance() 只会给对象分配内存, 并不会调用构造方法,所以输出int类型默认值 0 。
修改私有字段的值
通过 Unsafe 的putXXXX() 方法,我们可以修改任意私有字段的值
package com.suning.test;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @Author wangli
* @Descrintion:
* @Date : Created in 11:47 2019/5/7
* @
*/
public class Student {
private int age ;
Student(){
this.age = 10 ;
}
public int getAge() {
return age;
}
public static void main(String[] args)throws Exception {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
Student student = new Student();
Field age = student.getClass().getDeclaredField("age");
unsafe.putInt(student, unsafe.objectFieldOffset(age), 20);
System.out.println(student.getAge());
}
}
通过反射调用得到字段 age , 我们就可以使用Unsafe 将其值更改为任何其他 int 值,也可以通过反射直接修改
Unsafe 异常处理
一般情况下,我们有两种方式来抛异常
1. throws Exception
2. throw new Exception
通过 unsafe 方式不需要进行 try{}catch(){} 进行捕捉
public static void readFile()throws IOException{
throw new IOException();
}
public static void readFileUnsafe(){
unsafe.throwException(new IOException());
}
堆外内存的使用
我们都知道 Java 对象一般都是使用JVM 的堆内存来存储信息, 当内存不足时就会发生GC ,来释放内存,来给其他对象使用。
另外我们也可以考虑堆外内存来存储信息,当然堆外内存是不受 JVM 管理的,所以我们必须手动 allocate(分配) 和 free(释放)
假设我们要在堆外创建一个巨大的int 数组, 我们可以使用 allocateMemory() 方法来实现:
package com.suning.test;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
*
* @Author wangli
* @Descrintion:
* @Date : Created in 14:59 2019/5/7
* @
*/
public class OffHeapArray {
private static 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 (Exception e){
}
}
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);
}
public static void main(String[] args) {
OffHeapArray offHeapArray = new OffHeapArray(5);
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();
}
}
CAS(CompareAndSwap)底层方法
JUC 下面大量使用了 CAS 操作,它们底层是调用了 Unsafe 的 CompareAndSwapXXX() 方法。这种方式广泛运用于无锁算法, 与java 中标准的悲观锁机制相比, 它可以利用CAS 处理器指令提供极大的加速。
基于Unsafe 的compareAndSwapInt() 方法构建线程安全的计数器
我们定义了一个volatile 的字段count , 对所有线程可见,并在类加载时获取 count 在类中的偏移地址。
在 increment() 方法中,我们通过调用Unsafe 的compareAndSwapInt() 来尝试更新之前获取到的count 值, 如果没有被其他线程更新,则更新成功 , 否则不断尝试更新直到成功。
package com.test;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
/**
*
* @Author wangli
* @Descrintion:
* @Date : Created in 16:11 2019/5/7
* @
*/
public 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 (Exception e){
e.printStackTrace();
}
}
public void increment(){
int before = count ;
while (!unsafe.compareAndSwapInt(this,offset,before,before + 1)){ //自旋
before = count ;
}
}
public int getCount(){
return count ;
}
public static void main(String[] args) throws Exception{
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()
当一个线程正在等待某个操作时,JVM 调用 Unsafe 的 park() 方法来阻塞此线程
当阻塞中的线程需要再次运行时, JVM 调用 Unsafe 的 unpark() 方法来唤醒此线程。
LockSupport.park()/unpark() 底层就是调用的Unsafe 的方法。