本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
抽奖说明在文末。
前言
上一篇文章对引用做了基本介绍和简单分析,本篇看一下引用源码。
引用
Reference是引用对象的抽象基类。此类定义了常用于所有引用对象的操作,且需要由子类来调用其构造方法。 入参queue是ReferenceQueue引用队列实例,默认为 ReferenceQueue.NULL。
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
引用状态
Reference类把内存分为4种状态Active,Pending,Enqueued,Inactive。
Active,一般来说内存一开始被分配的状态都是 Active;
Pending 大概是指快要被放进队列的对象,也就是马上要回收的对象;
Enqueued 就是对象的内存已经被回收了,我们已经把这个对象放入到一个队列中,方便以后我们查询某个对象是否被回收;
Inactive就是最终的状态,不能再变为其它状态。
引用队列
引用队列作用就是存放待回收的引用对象,垃圾回收器扫描到将要进行回收时,其相应的引用包装类,即引用对象会被放入其注册的引用队列
成员变量
ReferenceQueue中内部成员变量如下:一个是NULL,一个是ENQUEUE
static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();
private static class Null<S> extends ReferenceQueue<S> {
boolean enqueue(Reference<? extends S> r) {
return false;
}
}
NULL表示没有注册ReferenceQueue的状态,ENQUEUED表示对应的Reference已经入队了。之后的逻辑操作中会以此来区分。
lock
用空的object类实现同步。
static private class Lock { };
private Lock lock = new Lock();
队列
在Reference中next关键变量用于描述结点指向的下一个元素,ReferenceQueue中还有一个head变量来指明队列的首部,queueLength变量指明队列长度。
入队
使用头插法,将引用r存入r.queue队列中。
boolean enqueue(Reference<? extends T> r) {
synchronized (lock) {
// 检测从获取这个锁之后,该Reference没有入队,并且没有被移除
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 将reference的queue标记为ENQUEUED
r.queue = ENQUEUED;
// 将r设置为链表的头结点
r.next = (head == null) ? r : head;
head = r;
queueLength++;
// 如果r的FinalReference类型,则将FinalRef+1
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
入队的方法使用了lock对象锁进行同步,将传入的r添加到队列中,并重置头结点为传入的节点。
出队
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
private Reference<? extends T> reallyPoll() {
Reference<? extends T> r = head;
if (r != null) {
head = (r.next == r) ?
null : r.next;
r.queue = NULL;
r.next = r;
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
poll方法把头节点弹出,设置新的头节点,并将引用r的下一个节点指向自身,这里实际是ReferenceStack。
移除
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException{
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
synchronized (lock) {
Reference<? extends T> r = reallyPoll();
if (r != null) return r;
long start = (timeout == 0) ? 0 : System.nanoTime();
// 死循环,直到取到数据或者超时
for (;;) {
lock.wait(timeout);
r = reallyPoll();
if (r != null) return r;
if (timeout != 0) {
// System.nanoTime方法返回的是纳秒,1毫秒=1纳秒*1000*1000
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
remove(long timeout)方法移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象或者超时才会返回。 首先进行出队操作,如果出队不为空则完成移除,若队空则根据超时时间来判断设定起始时间(纳秒),之后循环进行,交出锁并等待,之后进行出队,判断使是否出队成功,根据超时时间判断是否超时,如果超时就返回null。 除此之外还有一个默认超时为0的函数,超时设为0即没有超时限制,直到从队列中获取一个移除的Reference对象。
remove()移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象才会返回。
总结
ReferenceQueue一般用来与SoftReference、WeakReference或者PhantomReference配合使用,将需要关注的引用对象注册到引用队列后,便可以通过监控该队列来判断关注的对象是否被回收,从而执行相应的方法。主要使用场景:使用引用队列进行数据监控,队列监控的反向操作等。
抽奖说明
1.本活动由掘金官方支持 详情可见juejin.cn/post/701221…
2.通过评论和文章有关的内容即可参加,要和文章内容有关哦!
3.本月的文章都会参与抽奖活动,欢迎大家多多互动!
4.除掘金官方抽奖外本人也将送出周边礼物(马克杯一个和掘金徽章若干,马克杯将送给走心评论,徽章随机抽取,数量视评论人数增加)。