前言
文章
- 相关系列:《Java ~ Reference【目录】》(持续更新)
- 相关系列:《Java ~ Reference ~ ReferenceQueue【源码】》(学习过程/多有漏误/仅作参考/不再更新)
- 相关系列:《Java ~ Reference ~ ReferenceQueue【总结】》(学习总结/最新最准/持续更新)
- 相关系列:《Java ~ Reference ~ ReferenceQueue【问题】》(学习解答/持续更新)
- 涉及内容:《Java ~ Reference【总结】》
- 涉及内容:《Java ~ Reference ~ FinalReference【总结】》
一 ReferenceQueue(引用队列)源码及机制详解
类
ReferenceQueue(引用队列)类是专用于与Reference(引用)类(以及其子类)相互配合使用的一种队列。如果一个Reference(引用)类对象注册了ReferenceQueue(引用队列)类对象,且其所指对象被GC判定为可回收,则该Reference(引用)类对象会被置入ReferenceQueue(引用队列)类对象中(这里是简单叙述,实际上将Reference(引用)类对象加入引用队列还是有一个过程的,这个过程被称为Reference(引用)机制)。此时开发者就可以通过从ReferenceQueue(引用队列)类对象中获取Reference(引用)类对象来判断其对应的所指对象是否被GC回收,并同时执行一些自定义操作,例如回收一些资源等。
ReferenceQueue(引用队列)类可用于与Reference(引用)类相互配合跟踪对象被垃圾回收的活动,即判断对象是否已经被GC回收。
ReferenceQueue(引用队列)类用于和Reference(引用)类搭配使用。对于一个注册了引用队列的Reference(引用)类对象,当所指对象被GC判定为可回收,则该Reference(引用)类对象会被置入ReferenceQueue(引用队列)类对象中。
/**
* Reference queues, to which registered reference objects are appended by the
* garbage collector after the appropriate reachability changes are detected.
*
* @author Mark Reinhold
* @Description: 引用队列类
* @since 1.2
*/
public class ReferenceQueue<T> {
...
}
字段
queueLength(队列长度) —— 引用队列中Reference(引用)类对象的数量。
/**
* @Description: 队列长度
*/
private long queueLength = 0;
lock(锁) —— lock(锁)字段的值固定为一个内部类Lock(空)类的实例,专用于作为锁对象使用以保证Reference(引用)类对象不会出现线程安全问题…可直接用Object(对象)类不就行了么。
static private class Lock {
}
/**
* @Description: 锁对象,用于作为同步的条件使用
*/
private Lock lock = new Lock();
NULL(“空”引用队列) —— NULL(“空”引用队列)字段的值固定为一个内部类Null(空)类的实例,被作为标记值使用。如果一个Reference(引用)类对象没有注册ReferenceQueue(引用队列)类对象或已经从中出队,则会将其queue(引用队列)字段设置为NULL(“空”引用队列),因此可以通过判断Reference(引用)类对象的queue(引用队列)字段是否为NULL(“空”引用队列)来判断其是否注册及出队。
ENQUEUED(“入队”引用队列) —— ENQUEUED(“入队”引用队列)字段与NULL(“空”引用队列)字段相同,值都是Null(空)类的实例,被作为标记值使用。如果一个Reference(引用)类对象已经入队,则其queue(引用队列)字段设置为ENQUEUED(“入队”引用队列),因此可以通过判断Reference(引用)类对象的queue(引用队列)字段是否为ENQUEUED(“入队”引用队列)来判断其是否入队。
/**
* @Description: NULL类。该类继承自引用队列类,用于做默认值及标记值使用。
*/
private static class Null<S> extends ReferenceQueue<S> {
@Override
boolean enqueue(Reference<? extends S> r) {
// 入队操作直接返回false,意味Null引用对象永远都不可能成功入队。
return false;
}
}
static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();
head(头Reference(引用)类对象) —— 用于保存ReferenceQueue(引用队列)类对象中第一个Reference(引用)类对象。
/**
* @Description: 头引用对象,即引用队列的第一个引用对象
*/
private volatile Reference<? extends T> head = null;
构造方法
public ReferenceQueue() —— 普通的无参构造方法。
/**
* Constructs a new reference-object queue.
*/
public ReferenceQueue() {
}
方法
boolean enqueue(Reference<? extends T> r) —— 入队(false:失败,true:成功) —— 调用此方法可将Reference(引用)类对象加入队列中。该方法受synchronized关键字保护,因为ReferenceQueue(引用队列)类对象可能会在多线程的环境下使用,因此需要相应的同步手段确保来确保ReferenceQueue(引用队列)类对象中的数据不会出现错误。需要提及的是,enqueue()采用的是头插法,也就是说新入队的Reference(引用)类对象会成为新的头Reference(引用)类对象,具体源码详解如下:
/**
* @Description: 入队
*/
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;
// 如果当前引用对象是引用队列中的第一个引用对象,则将其自身作为后继引用对象(这一步是为了判断当前引用对象是否是引用队列中
// 的最后一个引用对象,在方法reallyPoll()中就存在该判断...但按理说直接判断next字段是否为null应该也是可以的,何必多次一举呢?或者
// 是由什么其它不可为null的原因?暂时没有发现)。可以看到引用队列使用的是头插法而非尾插法,即新引用对象会被作为头引用对象。
r.next = (head == null) ? r : head;
head = r;
queueLength++;
// 如果是最终引用类型,递增终引用的计数。
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
// 唤醒所有处于等待状态中的线程,即因为队中没有引用对象无法移除引用对象而一直等待着新引用对象入队的线程...
lock.notifyAll();
return true;
}
}
public Reference<? extends T> poll() —— 出队 —— 调用此方法可将ReferenceQueue(引用队列)类对象的头Reference(引用)类对象弹出,当存在头Reference(引用)类对象时返回头Reference(引用)类对象,否则返回null。poll()采用的是头出法,即后继ReferenceQueue(引用队列)类对象会成为新的头ReferenceQueue(引用队列)类对象。通过观察enqueue/poll()方法的运行机制可知,ReferenceQueue(引用队列)类并不是真正的队列,因为队列必须需要保证FIFO(先入先出),而ReferenceQueue(引用队列)类是FILO(先入后出)的运行方式,因此其本质实际是一个栈。
/**
* Polls this queue to see if a reference object is available. If one is
* available without further delay then it is removed from the queue and
* returned. Otherwise this method immediately returns <tt>null</tt>.
*
* @return A reference object, if one was immediately available,
* otherwise <code>null</code>
* @Description: 出队
*/
public Reference<? extends T> poll() {
// 这里存在一个DCL(Double Check Lock)双重检查锁,在reallyPoll()方法中同样有一次对head判空的判断。先在非同步的环境进行一次判断,
// 如果不存在头引用对象,则可直接返回null。但如果存在头引用对象,则需要在同步条件下在判断一次,以确保没有其它线程将头引用对象弹出队列。这么
// 的目的是为了提高性能,因为执行被synchronized修改的代码块性能会下降很多。
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
/**
* @Description: 出队(真)
*/
private Reference<? extends T> reallyPoll() { /* Must hold lock */
// 获取头引用(快照)。
Reference<? extends T> r = head;
if (r != null) {
// 获取头引用对象(快照)的下个引用对象(快照)。
@SuppressWarnings("unchecked")
Reference<? extends T> rn = r.next;
// 如果头引用对象(快照)与下个引用对象(快照)相等,则说明引用队列中只存在一个引用对象了,则直接把头节点设置为null,
// 因为把最后一个引用对象出队后,引用队列中就不存在引用对象了。如果不相等,则将下个引用对象(快照)设置为头头引用对象,
// 由此可知引用队列采用的是头出法。讲到这里也应该可以明白,引用队列并不是一个真正的队列,因为队列必须需要保证FIFO机制,
// 而引用队列是FILO的运行方式,因此其本质是一个栈。
head = (rn == r) ? null : rn;
// 设置头引用对象(快照)的注册引用队列为"空"引用队列,因为该引用对象已经完成了入队/出队操作,引用队列对其已经不在有
// 意义了。
r.queue = NULL;
r.next = r;
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
// 返回出队的头引用对象(快照)。
return r;
}
return null;
}
public Reference<? extends T> remove(long timeout)/public Reference<? extends T> remove() —— 限时移除/无限时移除 —— remove()方法的作用与poll()方法相同,都是将ReferenceQueue(引用队列)类对象的头Reference(引用)类对象弹出并返回,实际上remove()方法底层就是调用poll()方法实现的。两者的区别在于,如果头Reference(引用)类对象不存在,poll()方法会直接返回null,而remove()方法则是会进入阻塞状态,直至有Reference(引用)类对象入队后被唤醒并将之弹出。remove()方法有两个重载,但实际上可以看作一个,因为其中一个是通过调用另一个实现的。
/**
* Removes the next reference object in this queue, blocking until one
* becomes available.
*
* @return A reference object, blocking until one becomes available
* @throws InterruptedException If the wait is interrupted
* @Description: 无限时移除
*/
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
/**
* Removes the next reference object in this queue, blocking until either
* one becomes available or the given timeout period expires.
*
* <p> This method does not offer real-time guarantees: It schedules the
* timeout as if by invoking the {@link Object#wait(long)} method.
*
* @param timeout If positive, block for up to <code>timeout</code>
* milliseconds while waiting for a reference to be
* added to this queue. If zero, block indefinitely.
* @return A reference object, if one was available within the specified
* timeout period, otherwise <code>null</code>
* @throws IllegalArgumentException If the value of the timeout argument is negative
* @throws InterruptedException If the timeout wait is interrupted
* @Description: 限时移除,在指定时间内等待新的引用对象入队并将之删除。
*/
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 (; ; ) {
// 令当前线程陷入阻塞(本质是加入Monitor对象的WaitSet中),直至被执行enqueue()的线程唤醒。
lock.wait(timeout);
// 出队操作,此时可能返回null,因为入队唤醒的是所有阻塞线程,入队的引用对象可能已经被其它线程出队。
r = reallyPoll();
// 如果成功弹出,则返回,表示成功移除。
if (r != null) return r;
// 如果限时不为0,判断限时时间是否到达(限时为0时表示无限尝试直至成功为止)。
if (timeout != 0) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}