多线程与高并发编程(五)
一、AQS
- AQS内的state状态通过CAS来改变
- AQS内的队列的节点添加,也是通过compareAndSetTail(CAS)来改变
- 双向链表是因为需要看前面节点的状态
CAS操作是针对的tail也就是链表的尾巴,多个线程想加入链表就不需要整体链表都加sync了,这样效率比较高,下图中oldTail为期望值,node为改为的值,因为;;是死循环,所以不改变则一只自旋。
二、VarHandle
-
JDK1.9开始出现的,单词意思变量句柄,指向某个变量的引用
例如:Object o = new Object(); 这里的o是一个引用指向Oject开辟的空间,varHandle也代表这个引用。 -
如下图handle声明用法:
MethodHandles.lookup()为固定写法不管,findVarHandle的参数分别为那个值所在对象、值的名字、具体对象。这样就会使声明的VarHandle指向这个x的8了。
-
如下图handle用法:
handle.get(name) 获取handle指向name的值
handle.set(name, value) 将name的值修改
handle.compareAndSet(name, oldValue, newValue) CAS操作进行修改name的值
handle.getAndAdd(name, addValue) name的值加addValue
注:
这些操作都是原子性的,线程安全的!Handle的意义就在此,long类型等不能进行直接的原子操作!
VarHandle可以理解为直接操纵二进制码,所以效率非常高,9之前用反射操作的方式(每次使用都会自动检查)没有VarHandle快。
三、ThreadLocal(强引用,配合四中的弱引用来读,四、中具体讲了ThreadLocal)
- 用法
ThreadLocal tl = new ThreadLocal<>() 这里的T是任意对象
t1.get() 从ThreadLocal中取
t1.set(Object) 放入ThreadLocal
注:
ThreadLocal中的值和对象只是针对自己线程的。两个线程对同一个t1对象的读写是隔离的。 - 源码实现
①:以set()方法为例(比如T是一个叫Person的类)
内部是有一个Map
ThreadLocal.java:Thread.currentThread()获取了当前线程
这里的getMap(t)进入,发现获取了一个ThreadLocalMap,这个map是在Thread中:
总结上面Thread.currentThread.map(ThreadLocal, person)获取了当前线程中的一个map中,所以其实不是一个map用不同Thread做key的隔离,实际上是不同的Thread用不同的map。
这个ThreadLocalMap内可以存在多个key:value,key是ThreadLocal,value是set进来的对象,每个ThreadLocal只能set自己的,那么代表这个map是全局通用的,每个声明的ThreadLocal.set()时,都会对同一个map.put()内容。 - ThreadLocal用途
声明式事务,保证同一个connection(例如配置中有四项都是要连接数据库,就可以合并成为一个事务,但是不同的连接之间肯定不能合并为一个事务,那么第一个取的时候把这个connection放入本地线程的ThreadLocal中,以后的连接实际上都是从这个ThreadLocal中来取,保证是同一个链接,这部分知识应该是基础中的)
四、JAVA中的引用
引用一共四种:强、软、弱、虚
- 强引用:
普通的引用就是强引用(Object o = new Object())
垃圾回收时,有引用指向的空间一定不会被回收,也就是强引用不会被垃圾回收,除非下面你Object o = null,然后调用了System.gc(),System.in.read()(这句话是为了阻塞当前线程的,没有特殊含义,因为gc是跑在别的线程中的,这样阻塞一下main的线程,防止main结束,要是main结束了,gc也没啥意义了) - 软引用:
一个对象若只有一个软引用指向他,只有当系统堆内存不够用时才会被回收。
SoftReference。下图中m指向了一个softReference软引用,这个软引用指向了一个字节数组。
用途:做缓存用,比如说从数据库取了一堆数据,需要用直接存缓存取,空间不够了就可以去掉,平时很难用到。 - 弱引用(面试重点):
只要遭遇到gc就会回收
weakReference。下图中m只想了一个weakReference弱引用,这个弱引用指向了M对象,这个声明之后直接system.gc()就null了。
用途:这个M对象若除了这个弱引用还有个强引用指向它,那么当强引用消失之后,这个对象就应该被回收。
一般用在容器中
在ThreadLocal(基本上下图就是ThreadLocal面试能问到的最深处)中应用(实线是强引用,虚线是弱引用,tl是声明的ThreadLocal对象):
ThreadLocal中发现Entry(map中是entry这个key:value)的key实际上是一个弱引用指向ThreadLocal(因为内部的map的key是ThreadLocal),源码如下图(ThreadLocal内):
若key指向ThreadLocal是强引用,那么tl这个ThreadLocal的声明都不指向这个ThreadLocal时,那么因为ThreadLocal有强引用指向他,所以他永远不会被回收,而有些游戏服务器之类的,可能这个Thread永远不会停,那么这块区域就会永远不被回收,这块就发生了内存泄漏,单这个key若是弱引用的话,tl消失时这个指向的ThreadLocal就被回收了,因为tl都不指向这里了,map指向这个ThreadLocal没意义了所以无影响。
补充知识(内存泄漏:内存中有一块永远不会被回收。 OOM:内存分配不够使了。 完全不同的概念)。
ThreadLocal的坑:
key指向的弱引用,回收之后key变成了null,但是这个map永远存在的,所以这个value还是指向了另外的对象,所以ThreadLocal在每次使用完之后一定要手工tl.remove()回收ThreadLocal,否则这个map越来越大之后还是会在发生内存泄漏,慢慢的OOM了!如下图:
- 虚引用:
主要是处理堆外内存的。
PhantomReference,声明的条件苛刻,第二个必须是一个队列。
虚引用被垃圾回收发现直接就会被干掉,被干掉时放入声明时的那个队列中一个值,检测队列中出现了值就代表虚引用被干掉了,往队列中扔一个值就是虚引用被干掉通知的方式。
PhantomReference.get(),无论什么时候都拿不到值(不像上面那几个有引用就能拿到,这里有了也拿不到)。
用途:基本用不上,给写JVM人用的,写netty之类的也可以,一般他们都会监控这个队列,发现被通知之后就会做出一些处理
本四大项中虚引用第一张图,DirectByteBuffer是NIO中比较新的一个东西叫直接内存,也叫堆外内存,这部分不会被JVM虚拟机来调用,而是OS直接调用,因为这部分已经不属于JVM管辖,所以垃圾回收永远管不到他,所以当虚引用被垃圾回收时,我们监控发现队列中出现了值说明被回收了,那么我们手动去DirectByteBuffer也就是堆外内存中去回收那块没人指向的空间。
五、堆外内存的回收(超纲啦):
若是用的C、C++写的JVM那么用delete()或者free() C C++的函数。
JAVA中现在也有直接操作的,这个类叫Unsafe,不过新的JDK版本似乎不能直接访问了,回收内存用unsafe.freeMemory(long),分配内存用unsafe.allocateMemory(long),JUC的底层compareAndSwap都用到了这个类。
补充知识:垃圾回收不调用gc的话,一般来讲满了才会出来检查清扫。
北京马士兵教育学习笔记整理