描述
引用队列,在Reference类中辅助Reference的实现,对于注册ReferenceQueue的Reference对象,gc检测到可达性更改后,会将其加入到其中。
实现过程
构造方法
比较空洞的构造方法。。。。
public ReferenceQueue() { }
内部类
Null
用于辅助表示ReferenceQueue对应Reference的状况,通过继承ReferenceQueue实现,覆盖原有的enqueue入队方法,使其始终返回false。
private static class Null<S> extends ReferenceQueue<S> {
boolean enqueue(Reference<? extends S> r) {
return false;
}
}
对应在ReferenceQueue中有两个静态变量NULL,ENQUEUED。
static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();
NULL表示没有注册ReferenceQueue的状态,ENQUEUED表示对应的Reference已经入队了。之后的逻辑操作中会以此来区分。
Lock
同Reference中的使用一样,使用一个空的Object类来实现同步。
static private class Lock { };
private Lock lock = new Lock();
队列
在Reference得分析中我们看到了next引用变量,这一变量用于描述结点指向的下一个元素,除此之外,ReferenceQueue中还有一个head变量来指明队列的首部,queueLength变量指明队列长度。
Reference next;//Reference类
private volatile Reference<? extends T> head = null;//在ReferenceQueue中默认为null
private long queueLength = 0;//默认长度为0
入队
入队操作是由enqueue函数实现的。传入的是一个Reference,而且该函数必须由对应注册ReferenceQueue的Reference才能调用。
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
首先,通过传入的Reference获取对应的ReferenceQueue,存储在queue即自身this。通过判断queue的参数是否标记为NULL(没有注册ReferenceQueue),ENQUEUED(注册了但是对应的Reference已经在队里了),排除这些后将其返回false,入队操作失败。剩下的使用断言语句保证Reference中的ReferenceQueue与自身类的对应关系。这些是准备工作。
入队过程开始,先将queue标记为ENQUEUED防止重复入队。接着判断之前是否含有入队元素,如果有就将其放到传入的Reference的后面(next),并重新设置传入的Reference为队头,如果之前的队为空队列则将传入的Reference的next指向自身。最后如果传入的Reference是FinalReference的话,就使用addFinalRefCount使计数加一,便于jvm处理。一切完成后通知等待入队的进程并返回入队成功。
出队
出队操作是由reallyPoll函数实现的。
@SuppressWarnings("unchecked")
private Reference<? extends T> reallyPoll() { /* Must hold lock */
Reference<? extends T> r = head;
if (r != null) {
head = (r.next == r) ? null : r.next; // Unchecked due to the next field having a raw type in Reference
r.queue = NULL;
r.next = r;
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
也很简单的操作,先判断出队的对象不为空,也就是队首,判断出队后队中是否还有其他的元素,如果没有(出队的Reference中next指向自己)就将head置空,如果有就将head指向出队的Reference的next,之后清除出队Reference的引用队列,将其next指向自身,数量减一,判断出队的Reference是否为FinalReference,如果是就将对应的计数减一。返回出队的Reference。将出队方法封装:
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
移除
这一方法从队列中移除一个Reference。当队列为空时就等待一段时间直到移除一个Reference或者超时。timeout为超时时间。
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) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}
首先进行出队操作,如果出队不为空则完成移除,若队空则根据超时时间来判断设定起始时间(纳秒),之后循环进行,交出锁并等待,之后进行出队,判断使是否出队成功,根据超时时间判断是否超时,如果超时就返回null。
除此之外还有一个默认超时为0的函数,超时设为0即没有超时限制,直到从队列中获取一个移除的Reference对象。
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
小结
ReferenceQueue的实现很简单,就是一个队列的维护和相关的同步操作,但是其在Reference的使用中十分重要。充分理解ReferenceQueue有利于我们对Reference的理解。