老王:小陈啊,从今天开始我们就要进入 《结丹篇》 了,在这一篇章里面,要注意听讲啊,对后面的每一个阶段的理解来说都至关重要的......
小陈:好的,老王,前面的《筑基》、《练气》两篇我已经重复看了好几遍了,早就期待进入下一个境界了......
老王:好,既然你这么有信心,那我们就正式进入主题了。
老王:首先啊,这一篇我们需要从JDK底层的unsafe开始讲起,后面的篇章很多都是依赖于unsafe提供的操作的。
JDK底层的unsafe是什么?
小陈:哦,unsafe?unsafe是个啥东西啊? 我还没有接触过
老王:说起unsafe啊,是JDK提供的一个工具类,unsafe里面的方法大多是native方法,你可以理解为unsafe类是JDK给你提供的一个直接调用操作系统底层功能的一个工具类,unsafe提供了非常多操作系统级别的方法。
(1)比如说通过unsafe可以让操作系统直接给你分配内存、释放内存。
(2)突破java语法本身的限制,直接从内存级别去操作堆里面的某个对象的数据;
(3)调用操作系统的CAS指令,实现CAS的功能
(4)操作系统层次将线程挂起和恢复
(5)提供操作系统级别的内存屏障(之前说过的Load屏障和Store屏障),读取数据强制走主存,修改数据直接刷新到主存
总之unsafe就相当于JDK给你提供的一个直接跟操作系统打交道的一个工具类,通过unsafe可做一些非常底层的指令和行为。
小陈:额,竟然可以直接通过unsafe分配内存,那岂不是不需要通过堆内存也可以直接分配内存了吗?这样岂不是很危险,万一使用者分配大量的内存,没有及时回收,岂不是很容易造成内存溢出的风险?又或者分配了内存,但是忘记回收了,容易造成内存泄露啊
老王:是啊,unsafe提供了很多操作系统级别的方法,在提供使用者便利的同时,也是隐藏着很多风险的。
但是unsafe提供的这些操作系统级别的方法对于JDK底层的一些工具类、上层的一些框架来说在实现层方便了许多。
比如著名的并发基础工具类AQS底层就是通过unsafe提供的CAS操作来进行加锁的,加锁失败的线程又是通过unsafe提供的park、unpark操作将线程挂起和唤醒的,还有一些非常著名的开源框架比如netty分配直接内存的方式底层也还是通过unsafe分配直接内存。
老王:所以啊,去了解一下unsafe底层的一些操作还是很有必要的,对于后面我们要学习的很多线程安全的类,比如Atomic系列的类、基于AQS一些列的同步工具还是很有必要的,因为这些底层都是通过unsafe提供的操作去实现的。
小陈:哦哦,原来unsafe类这么重要啊......
老王:下面啊,我就分几类将一些unsafe提供的一些重要功能
unsafe直接分配和释放内存
代码语言:javascript复制// 分配bytes大小的堆外内存
public native long allocateMemory(long bytes);
// 还可以执行从address处开始分配,分配bytes大小的堆外内存
public native long reallocateMemory(long address, long bytes);
// 释放allocateMemory和reallocateMemory申请的内存块
public native void freeMemory(long address);
// 将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
// 相当于直接让你在内存级别直接给这个对象的变量赋值了
public native void setMemory(Object o, long offset, long bytes, byte value);
// 设置给定内存地址的long值
// 相当于直接在内存级别给address后面的8个字节赋值
public native void putLong(long address, long x);
// 获取指定内存地址的long值
// 相当于获取address后面的8个字节的值,然后转化为十进制的long值给你
public native long getLong(long address);
// 设置或获取指定内存的byte值
// 相当于获取address后面一个字节的数据,转化成十进制返回给你
public native byte getByte(long address);
// 直接在内存级别给adderess地址后面的1个字节设置
public native void putByte(long address, byte x);
老王:这里提供一些操作系统级别的直接申请内存、释放内存的方式。
同时不受java语法的限制,提供内存级别的直接获取数据,修改数据的方式;unsafe操作内存这块内存我们目前只需要了解即可,对我们后续并发的学习影响不大。
老王:这里说可以直接分配内存,释放内存,直接从内存级别修改数据的操作理解了没?
小陈:这里说的通过操作系统级别的分配内存,释放内存,其实就是调用操作系统底层的api去申请和释放内存吧。
老王:是的,unsafe确实是通过调用操作提供的能力直接去申请和释放内存的。
小陈:上面说的不受java语法限制,直接修改内存数据是咋回事?
老王:比如说某个对象的private int value属性,这里的value属性是private的,如果这个对象没有提供对应的访问方法,在对象外部是访问不到这个value属性的对不对?
小陈:嗯嗯,是的,正常来说只有在对象的内部可以访问这个private属性,外部是访问不到的。
老王:然而这个对象的数据本质上还是保存在内存里面的,而unsafe提供了直接对某个内存地址读取、修改的操作,这样就可以突破java语法的限制了。
小陈:哦哦,原来是这样啊,相当于就是直接通过内存地址address,找到这块内存然后直接操作这个内存块的数据了。
老王:嗯嗯,没错;就是直接操作内存块的数据,接下来我们再讲讲unsafe提供的cas操作。
unsafe提供的CAS操作
老王:这块内容比较重要,JUC提供的很多Atomic原子类、基于AQS实现的并发工具,底层都是通过CAS操作去实现的。下面我们就说说unsafe提供的cas操作:
老王:假如目前有一个Test类是这样子的:
代码语言:javascript复制public class Test {
private DemoClass demo;
private int intValue;
private long longValue;
}
有一个Test类的对象 Test o = new Test();
这个时候想要突破java语法的限制,直接修改对象o的private修饰的demo属性。可以通过CAS操作直接去修改对象o里面的demo属性,使用unsafe提供的下面方法:
(1)o就是你要操作的对象
(2)offset就是demo属性在对象o内部的位置,或者偏移量
(3)expected就是demo期待的值
(4)x就是你希望设置的新值,只有demo的值 == expect的时候,才能将demo的值设置成x
代码语言:javascript复制public final native boolean compareAndSwapObject(Object o,
long offset,
Object expected,
Object x);
执行CAS操作大致是这样的,根据 对象o的地址,demo属性相对于o的偏移量offset,直接计算得到demo所在内存的位置,然后直接将demo的值从内存取出进行CAS(比较替换操作):
同理对于,执行CAS操作替换Test类对象o内部的int值和long值,unsafe提供了如下两个方法:
代码语言:javascript复制public final native boolean compareAndSwapInt(Object o,
long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o,
long offset,
long expected,
long x);
底层执行CAS替换的原理跟上面画图将的demo其实是一样的,这里就不再赘述了。
unsafe将线程挂起和恢复
unsafe类提供类将一个线程挂起、讲一个挂起的线程唤醒的方法,分别是park和unpark,我们看如下的代码:
park方法
代码语言:javascript复制//线程调用该方法,线程将一直阻塞直到被唤醒,或者超时,或者中断条件出现。
public native void park(boolean isAbsolute, long time);
(1) isAbsolute是否是绝对时间,当isAbsolute == true ,后面time的时间单位是ms;当为false的时候,后面time参数的时间单位是ns。
(2)time > 0时候,表示大概要将线程挂起time的时间,过了时间后自动将线程唤醒。当time = 0的时候,表示一直将线程挂起,直到有人调用unpark方法将线程唤醒。
unpark方法
代码语言:javascript复制public native void unpark(Object thread);
直接将正在被挂起的thread线程唤醒,让它继续干活
LockSupport
LockSupport是对unsafe中park和unpark功能封装的一个工具类,提供了阻塞和唤醒功能。
我们可以直接使用LockSupport的方法达到挂起和恢复线程的效果,LockSupport方法的源码如下:
代码语言:javascript复制public class LockSupport {
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
// 这里直接调用unsafe的park方法将线程挂起
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void unpark(Thread thread) {
if (thread != null)
// 直接调用unsafe的unpark方法将线程唤醒
UNSAFE.unpark(thread);
}
}
老王:由于我们自己编写的java程序不能直接使用unsafe工具类,所以啊JDK还是有一些工具类对unsafe类的功能进行封装,然后我们就直接使用这些封装的工具类即可。
内存屏障
unsafe提供了几种内存屏障:
代码语言:javascript复制// 在该方法之前的所有读操作,一定在load屏障之前执行完成
public native void loadFence();
// 在该方法之前的所有写操作,一定在store屏障之前执行完成
public native void storeFence();
// 在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个的合体功能
public native void fullFence();
老王:小陈啊,关于上述讲解的unsafe提供的几类操作系统级别的功能,理解清楚了吗?
小陈:恩恩,大致上清楚了,我看比较重要的还是:内存级别操作数据,cas操作,线程挂起park和唤醒unpark。
老王:那我们本章就先到这里了,我们下一章节再聊。
小陈:我们下一章见。
目录
JAVA并发专题 《筑基篇》
1.什么是CPU多级缓存模型?
2.什么是JAVA内存模型?
3.线程安全之可见性、有序性、原子性是什么?
4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?
JAVA并发专题《练气篇》
5.volatile怎么保证可见性?
6.什么是内存屏障?具有什么作用?
7.volatile怎么通过内存屏障保证可见性和有序性?
8.volatile为啥不能保证原子性?
9.synchronized是个啥东西?应该怎么使用?
10.synchronized底层之monitor、对象头、Mark Word?
11.synchronized底层是怎么通过monitor进行加锁的?
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
13.synchronized怎么保证可见性、有序性、原子性?
JAVA并发专题《结丹篇》
- JDK底层Unsafe类是个啥东西?
15.unsafe类的CAS是怎么保证原子性的?
16.Atomic原子类体系讲解
17.AtomicInteger、AtomicBoolean的底层原理
18.AtomicReference、AtomicStampReference底层原理
19.Atomic中的LongAdder底层原理之分段锁机制
20.Atmoic系列Strimped64分段锁底层实现源码剖析
JAVA并发专题《金丹篇》
21.AQS是个啥?为啥说它是JAVA并发工具基础框架?
22.基于AQS的互斥锁底层源码深度剖析
23.基于AQS的共享锁底层源码深度剖析
24.ReentrantLock是怎么基于AQS实现独占锁的?
25.ReentrantLock的Condition机制底层源码剖析
26.CountDownLatch 门栓底层源码和实现机制深度剖析
27.CyclicBarrier 栅栏底层源码和实现机制深度剖析
28.Semaphore 信号量底层源码和实现机深度剖析
29.ReentrantReadWriteLock 读写锁怎么表示?
- ReentrantReadWriteLock 读写锁底层源码和机制深度剖析
JAVA并发专题《元神篇》并发数据结构篇
31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?
32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?
33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?
34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?
35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?
36.DelayQueue 底层源码剖析,延时队列怎么实现?
37.SynchronousQueue底层原理解析
JAVA并发专题《飞升篇》线程池底层深度剖析
- 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?
39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?
40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?
- ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?
- ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?
- ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?
- ThreadPoolExecutor shutdown、shutdownNow内部核心流程
- 再回头看看为啥不推荐Executors提供几种线程池?
- ThreadPoolExecutor线程池篇总结