【ReentrantLock源码】:
【AQS源码】:
【公平与非公平】:
【公平】:
线程想要获得一把锁,乖乖的去这把锁的等待队列里排队————公平。
【非公平】:
线程想要获得一把锁,不去排队,去插队,抢到就算我的。
【】:
final Thread current = Thread.currentThread(); //拿到当前线程。
【理解state】:
int c = getState();
》〉》
protected final int getState() {
return state;
}
》〉》
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private volatile int state;
}
AQS的核心就是这个state 以及监控state的一个双向链表(链表里每个节点装的都是线程)。
//这个state是volatile的 , 可以保证线程之间是可见的。根据子类的不同实现有不同的意义。
state下面跟了一个队列,这个队列是AQS内部所维护的一个队列。这个队列每一个里面都是一个Node , 这个Node是AQS的一个内部类:
static final class Node { ...
volatile Thread thread; //最重要的一个属性 , 所以这个队列是一个线程队列。
volatile Node prev; //前一个节点。
volatile Node next; //后一个节点。
... }
//双向链表。
【入队出队】:
并非是将整个队列用sync锁住 , 而是用CAS的方式 , 几个线程都往尾巴上加,谁先谁后呢?——用CAS的方式
【 nonfairTryAcquire 】:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) { //用CAS的方式,期望值是0,尝试将其改成1.
setExclusiveOwnerThread(current); //假如改成了,把当前线程改为独占state的线程,就说明我已经得到了这把锁,而且这把锁是互斥的。
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //如果说当前线程已经是独占的了。
int nextc = c + acquires; //往后加,加个1。
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
【画图方式】:
1)方法之间的调用图;
2)类之间的类图。
【day5课前复习】:
state代表什么——要看子类如何去实现。
AQS核心一个共享的数据(state),一堆互相抢夺的线程(在队列中)。
【AQS源码复习(1)】:
【 addWaiter 】:
Node.EXCLUSIVE————排它锁 ;
Node.SHARED————共享锁 ;
【AQS源码复习(2)】:
//用CAS去操作head和tail ;
【 VarHandle 】:
是1.9之后才有的;——指向某个变量的引用。本身已经有一个引用指向变量了,为什么再用一个VarHandle引用去指向变量呢?
handle = MethodHandles.lookup().findVarHandle(T01_HelloVarHandle.class, "x", int.class);
//到哪一个类中去找int类型的“x”变量。
//这一句代码执行之后 , 通过x可以找到8 , 通过handle也可以找到8 。
【 意义所在 】:
handle.get(t); //直接读
handle.set(t,9); //直接写
handle.compareAndSet(t, 9, 10);
handle.getAndAdd(t, 10);
//我们可以通过操作handle来改变所指向的值。
像x = x + 10 ; //这种操作如果想保证线程安全的话是需要加锁的 , 但是通过VarHandle就不需要(可以完成原子性的线程安全的操作)。
【反射和VarHandle的区别 】:
VarHandle效率要高的多,反射每次用之前都要做检查,VarHandle不需要(直接操作二进制码)。
【 总结VarHandle 】:
1):
普通属性原子操作;
2):
比反射快,直接操纵二进制码;
【 ThreadLocal 】:
static ThreadLocal<Person> tl = new ThreadLocal<>(); //Person这个值是线程独有的。
//设置的时候得设置自己线程能访问到的。
【解释双线程】:
线程一在ThreadLocal中设置了tl的值 , 但是线程二中却无法get , 这是为什么呢???
【 set源码 】:
public void set(T value) {
Thread t = Thread.currentThread(); //先拿到当前线程;
ThreadLocalMap map = getMap(t); //多了一个容器map
if (map != null)
map.set(this, value); //this是当前的ThreadLocal对象。
else
createMap(t, value);
}
map中的K是当前的ThreadLocal对象 , V是你要放的值。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
==》〉
Thread类中:
ThreadLocal.ThreadLocalMap threadLocals = null;
【总结SET的位置】:
//原来是设置到了当前线程的某一个Map之中去了。
【ThreadLocal的SET实质】:
设置到当前线程的Map中。
【 ThreadLocal用途 】:
声明式事务 , 保证同一个Connection 。
【 强引用 】:
//其实Java的引用一共有四种( 强、软、弱、虚 );
Obj o = new Obj() ; ————//此即强引用。只要有引用指向这个对象 , 垃圾回收器就不会去回收。
【 软引用 】:
SoftReference<byte[]> sr = new SoftReference<>(new byte[1024 * 1024 * 10]);
//变量sr指向软引用对象 , 该对象又指向10M大小的字节数组。
//如果10M的数组被回收了 , 那么sr.get( ) 得到的应该是一个空值。
【 软引用实质 】:
当有一个对象被软引用所指向的时候 , 只有系统内存不够用的时候 , 才会回收这个对象。
//内存够用的话是不会回收的。
【内存分配】:
//堆内存直接分配20M (最多20M , 最小也是20M ,那么就是20M )
/**
* 软引用
* 软引用是用来描述一些还有用但并非必须的对象。
* 对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。
* 如果这次回收还没有足够的内存,才会抛出内存溢出异常。
* -Xmx20M
*/
package Ten_Class.t05.no157;
import java.lang.ref.SoftReference;
public class T02_SoftReference {
public static void main(String[] args) {
SoftReference<byte[]> sr = new SoftReference<>(new byte[1024 * 1024 * 10]);
System.out.println(sr.get());
System.gc(); //因为VM Options中设置了20M ,所以这里不会被回收。
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sr.get());
//再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
byte[] b = new byte[1024 * 1024 * 12]; //这里会被回收。
System.out.println(sr.get());
}
}
//软引用非常适合缓存使用
【最终输出】:
【 弱引用 】:
【概念】:
弱引用只要遭遇到gc就会回收。
【案例】:
/**
* 弱引用遭到gc就会回收
*/
package Ten_Class.t05.no158;
import Ten_Class.t05.no156.M;
import Utils.SleepHelper;
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
public class T03_WeakReference {
public static void main(String[] args) {
WeakReference<M> wr = new WeakReference<>(new M());
System.out.println(wr.get());
System.gc();
SleepHelper.sleepSeconds(1);
System.out.println(wr.get());
}
}
【作用】:
一般用在容器里。——一个典型的应用就是ThreadLocal
【 图解弱引用 】:
tl是强引用指向ThreadLocal对象 , 而key是弱引用指向ThreadLocal对象。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry的KEY是通过弱引用指向的ThreadLocal对象。
【ThreadLocal大坑】:
tl.remove();
//一定要养成好习惯——ThreadLocal用完了一定要进行remove , 否则会造成内存泄漏。
【 虚引用 】:
【作用】:
管理堆外内存。
【实质】:
是给写虚拟机的人用的。
【示例】:
/**
* 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,
* 也无法通过虚引用来获取一个对象的实例。
* 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
* 虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,
* 那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,
* 弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。
* <p>
* jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,
* 而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),
* 所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,
* 会在堆内存分配一个对象保存这个堆外内存的引用,
* 这个对象被垃圾收集器管理,一旦这个对象被回收,
* 相应的用户线程会收到通知并对直接内存进行清理工作。
* <p>
* 事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,
* DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。
*/
package Ten_Class.t05.no159;
import Ten_Class.t05.no156.M;
import Utils.SleepHelper;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
/**
* //————务必先将堆内存设置成20M 。
* */
public class T04_PhantomReference {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
System.out.println(phantomReference.get());
ByteBuffer b = ByteBuffer.allocateDirect(1024);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
SleepHelper.sleepSeconds(1);
System.out.println(phantomReference.get());
}
}).start();
//垃圾回收线程
new Thread(() -> {
while (true) {
Reference<? extends M> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
}
}
}).start();
SleepHelper.sleepSeconds(1);
}
}
//最后一直在永无休止地打印null …
【总结作用】:
当传入的一参对象被干掉的时候 ,你会收到一个通知( 通知的方式是往队列里扔进一个值 )。
//如果你想得到通知,你就不断去检测这个队列里面有没有值。 (如果有值说明某个虚引用被回收了)
【 虚引用 和 弱引用 的区别 】:
【弱】:
里面有值 , 你去get的时候,还是可以get到的;
【虚】:
你是get不到里面的值的。拿不着为什么这么设计呢——只是为了给你一个通知( 通知的时候放到队列里 )。
//虚引用一般是写JVM的人使用的 , 当监测到QUEUE中加入值的时候,可以做出相应的处理 。
【 DirectByteBuffer 】:
NIO里面的 , 叫做——直接内存。直接内存是不被JVM虚拟机直接管理的内存(被操作系统所管理),又叫做堆外内存。
【Q】:
当DirectByteBuffer被设置为NULL的时候 ,你怎么去回收堆外内存呢???——这个时候可以使用虚引用,当我们监测到这个虚引用被垃圾回收器回收的时候,做出相应的处理去回收堆外内存。
【 Unsafe 】:
[ 方法copyMemory ]:
//直接访问内存~
[ allocateMemory ]:
//直接分配内存。
[ freeMemory ]:
//手动回收——( 你直接分配内存的时候必须要手动回收 )。
【 总结 】:
【容器图】:
//容器图一定要背过!!!