AQS(CLH)
底层:CAS+volatile
图解
aqs核心上是一个state(volatile),以及监控这个state的一个双向链表,每个链表有一个节点,每个节点装的是线程,那么每个线程要获得锁,要等待,都要进入到这个等待队列中。
(在添加队列尾要关注前置节点,进行CAS防止线程打断,自旋等待加到尾部,替代了sync进行锁链表,这就是效率高的核心)
- ReentrantLock:state记录了线程重入了多少次,当变成0即释放。
- 加入等待队列:addWaiter方法中,用到jdk1.9 varHandle类型(指向变量的引用),可进行原子性操作(操作了普通属性),类似反射,但效率高。
varHandle:
1.普通属性原子性操作
2.比反射快,直接操作二进制码
int x = 8;
private static VarHandle handle;
static {
try {
handle = MethodHandles.lookup().findVarHandle(T01_HelloVarHandle.class, "x", int.class);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
T01_HelloVarHandle t = new T01_HelloVarHandle();
//plain read / write
System.out.println((int)handle.get(t));
handle.set(t,9);
System.out.println(t.x);
handle.compareAndSet(t, 9, 10);
System.out.println(t.x);
handle.getAndAdd(t, 10);
System.out.println(t.x);
}
ThreadLocal
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。
ThreadLocal是除了加锁这种同步方式之外的一种保证,一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量,这样就不会存在线程不安全问题。
源码
- set
Thread.currentThread.map(ThreadLocal,person)
设置到了当前线程的map里。 - 用途
声明式事务,保证同一个Connection
public class ThreadLocal2 {
//volatile static Person p = new Person();
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tl.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
tl.set(new Person());
}).start();
}
static class Person {
String name = "zhangsan";
}
}
强软弱虚
-
强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:
Object strongReference = new Object();
当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 -
软引用:内存不够时进行回收,多用于缓存
软引用是用来描述一些还有用但并非必须的对象。
对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。 如果这次回收还没有足够的内存,才会抛出内存溢出异常。-Xmx20M -
弱引用
只要遭遇GC,直接回收,一般用于容器
public static void main(String[] args) {
WeakReference<M> m = new WeakReference<>(new M());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
ThreadLocal<M> tl = new ThreadLocal<>();
tl.set(new M());
tl.remove(); //要清除
}
为什么Entry要使用弱引用?
若是强引用,即使tl=null,但key的引用依然指向ThreadLocal对象,所以会有内存泄漏,而使用弱引用则不会。
但还是会有内存泄露存在,ThreadLocal被回收,key的值变成null,则导致整个value再也无法被访问到,因此依然存在内存泄露。
所以最终一定要remove
- 虚引用-堆外内存(给写jvm的人所用)
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着,弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null, 弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。
jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,而直接内存是在堆内存之外(其实是内存映射文件),所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,会在堆内存分配一个对象保存这个堆外内存的引用, 这个对象被垃圾收集器管理,一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。