Javaソースコード分析とインタビューの質問-DelayQueueソースコード分析

関連するブログ、ムークラス参照列この一連のJavaソースコードとシステムメーカーは簡潔Zhentiインタビュアー
この列の下には、GitHubのアドレスです:
ソースは解決:https://github.com/luanqiu/java8
記事デモ:HTTPS:// GitHubの。 com / luanqiu / java8_demo
クラスメートは必要に応じてそれを見ることができます)

Javaソースコード分析とインタビューの質問-DelayQueueソースコード分析

はじめ
に前述のブロッキングキューは、十分なリソースがあるときにすぐに実行されます。この章で説明したキューは特別で、遅延キューの一種であり、実行の遅延を意味します。たとえば、5秒後に実行を遅延する時間を設定してから実行できます。和解など。

1全体的なデザイン

DelayQueue遅延キューの下部はロックする機能を使用しています。たとえば、現在の時刻で実行を5秒間遅らせたい場合、現在のスレッドは5秒間スリープします。5秒後にスレッドが起こされると、リソースを取得できる場合、スレッドはすぐに実行できます。原理的にはシンプルなようですが、内部実装が非常に複雑で、実行中のリソースが足りず、同時に複数のスレッドが起こされた場合、どのようにキューに入れるのかなど、多くの困難があります。たとえば、いつブロックしますか?実行開始等はいつですか?次に、ソースコードの観点から実装方法を見ていきます。

1.1クラスのメモ

クラスの注釈は比較的単純であり、3つの概念のみが言及されています。

  1. キュー内の要素は、期限が切れたときに実行されます。キューの先頭に近いほど、期限が短くなります。
  2. 有効期限が切れていない要素は取得できません。
  3. 空の要素は許可されていません。

これらの3つの概念は、実際には3つの問題です。以下では、これら3つのポイントがどのように実装されるかを見ていきます。

1.2クラス図

DelayQueueのクラス図は前のキューと同じですが、言うまでもなく、重要な点は、DelayQueueクラスが次のようにジェネリックであることです。

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {

ジェネリックから、DelayQueueの要素はDelayedのサブクラスである必要があることがわかります。Delayedは、遅延機能を表現する主要なインターフェースです。Comparableインターフェースを継承し、次のように残りの時間の期限を定義します。

public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}

つまり、DelayQueueキュー内の要素は、DelayedインターフェースとComparableインターフェースを実装し、getDelayメソッドとcompareToメソッドをオーバーライドする必要があります。そうしないと、コンパイル時に、エレメントがDelayedインターフェースを強制する必要があることをコンパイラーが思い出させます。

さらに、DelayQueueは、SynchronousQueueキューに非常に類似したPriorityQueueキューの多くの機能も使用します。他の基本クラスの多くのロジックが再利用されます。コード例は次のとおりです:
ここに画像の説明を挿入
PriorityQueue中国語は優先キューと呼ばれ、ここでの役割は有効期限に従って優先順位を付けると、最初に有効期限が切れるように実行され、クラスコメントの最初のポイントを達成するために使用されます。

ここでの再利用の考え方は非常に重要です。LinkedHashMapがHashMapを再利用する機能、SetがMapを再利用する機能、DelayQueueがここでPriorityQueueを再利用する機能など、ソースコードでこの考え方によく遭遇します。 。要約すると、再利用する場合は何をする必要がありますか?

  1. 可能な限り再利用できる関数を抽象化し、拡張可能な場所を開く必要があります。たとえば、配列を操作するメソッドでは、HashMapはLinkedHashMapの最初の後に多くのメソッドを開きます。これは、LinkedHashMapがソート、削除などを行うのに便利です。;
  2. LinkedHashMapで使用される継承、SetとDelayQueueで使用される組み合わせなど、組み合わせまたは継承を使用して再利用します。組み合わせの意味は、再利用可能なクラスに依存することです。

2デモ

みんなの理解を促進するために、私はデモをデモするデモを書きました:

public class DelayQueueDemo {

	// 队列消息的生产者
  	static class Product implements Runnable {
    	private final BlockingQueue queue;
    	public Product(BlockingQueue queue) {
      		this.queue = queue;
    	}
    
    	@Override
    	public void run() {
      		try {
        		log.info("begin put");
        		long beginTime = System.currentTimeMillis();
        		queue.put(new DelayedDTO(System.currentTimeMillis() + 2000L,beginTime));//延迟 2 秒执行
        		queue.put(new DelayedDTO(System.currentTimeMillis() + 5000L,beginTime));//延迟 5 秒执行
        		queue.put(new DelayedDTO(System.currentTimeMillis() + 1000L * 10,beginTime));//延迟 10 秒执行
        		log.info("end put");
      		} catch (InterruptedException e) {
        		log.error("" + e);
      		}
    	}
  	}
  	
	// 队列的消费者
  	static class Consumer implements Runnable {
    	private final BlockingQueue queue;
    	public Consumer(BlockingQueue queue) {
      		this.queue = queue;
    	}
 
    	@Override
    	public void run() {
      		try {
        		log.info("Consumer begin");
        		((DelayedDTO) queue.take()).run();
        		((DelayedDTO) queue.take()).run();
        		((DelayedDTO) queue.take()).run();
        		log.info("Consumer end");
      		} catch (InterruptedException e) {
        		log.error("" + e);
      		}
    	}
  	}
 
  	@Data
  	// 队列元素,实现了 Delayed 接口
  	static class DelayedDTO implements Delayed {
    	Long s;
    	Long beginTime;
    	public DelayedDTO(Long s,Long beginTime) {
     		this.s = s;
      		this.beginTime =beginTime;
    	}
 
    	@Override
    	public long getDelay(TimeUnit unit) {
      		return unit.convert(s - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    	}
 
    	@Override
    	public int compareTo(Delayed o) {
      		return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    	}
 
    	public void run(){
      		log.info("现在已经过了{}秒钟",(System.currentTimeMillis() - beginTime)/1000);
    	}
  	}
  	
	// demo 运行入口
  	public static void main(String[] args) throws InterruptedException {
    	BlockingQueue q = new DelayQueue();
    	DelayQueueDemo.Product p = new DelayQueueDemo.Product(q);
    	DelayQueueDemo.Consumer c = new DelayQueueDemo.Consumer(q);
    	new Thread(c).start();
    	new Thread(p).start();
  	}
}

打印出来的结果如下:
06:57:50.544 [Thread-0] Consumer begin
06:57:50.544 [Thread-1] begin put
06:57:50.551 [Thread-1] end put
06:57:52.554 [Thread-0] 延迟了2秒钟才执行
06:57:55.555 [Thread-0] 延迟了5秒钟才执行
06:58:00.555 [Thread-0] 延迟了10秒钟才执行
06:58:00.556 [Thread-0] Consumer end

このコードを作成する目的は、主に遅延実行の例を示すことです。

  1. DelayedDTOなど、新しく作成されたキューの要素は、Delayedインターフェイスを実装する必要があります。getDelayメソッドでは、有効期限までの残り時間をメソッドに実装しました。
  2. コードのProductおよびConsumerに対応するキュー要素のプロデューサーおよびコンシューマーを定義します。
  3. メインメソッドに対応する、プロデューサーとコンシューマーの初期化と管理。

これは単なるデモですが、実際の作業では、基本的にはDelayQueueを使用しますが、コードをより包括的かつ包括的に記述する場合は、DelayQueueがどのようにputとtakeを実装するかを見てみましょう。

3データを置く

putを例にとると、Putはofferメソッドを呼び出します。offerのソースコードは次のとおりです。

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    // 上锁
    lock.lock();
    try {
        // 使用 PriorityQueue 的扩容,排序等能力
        q.offer(e);
        // 如果恰好刚放进去的元素正好在队列头
        // 立马唤醒 take 的阻塞线程,执行 take 操作
        // 如果元素需要延迟执行的话,可以使其更快的沉睡计时
        if (q.peek() == e) {
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

PriorityQueueのofferメソッドが実際に一番下で使用されていることがわかります。見てみましょう。

// 新增元素
public boolean offer(E e) {
    // 如果是空元素的话,抛异常
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    // 队列实际大小大于容量时,进行扩容
    // 扩容策略是:如果老容量小于 64,2 倍扩容,如果大于 64,50 % 扩容
    if (i >= queue.length)
        grow(i + 1);
    size = i + 1;
    // 如果队列为空,当前元素正好处于队头
    if (i == 0)
        queue[0] = e;
    else
    // 如果队列不为空,需要根据优先级进行排序
        siftUp(i, e);
    return true;
}

// 按照从小到大的顺序排列
private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x;
    // k 是当前队列实际大小的位置
    while (k > 0) {
        // 对 k 进行减倍
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        // 如果 x 比 e 大,退出,把 x 放在 k 位置上
        if (key.compareTo((E) e) >= 0)
            break;
        // x 比 e 小,继续循环,直到找到 x 比队列中元素大的位置
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

ご覧のとおり、PriorityQueueのofferメソッドは主に3つのことを行います。

  1. 新しく追加された要素を判断します。
  2. キューを展開します。展開戦略は、セットの展開戦略と非常に似ています。
  3. 要素のcompareToメソッドに従って並べ替えます。最終的な並べ替えの結果が小さいものから大きいものになることを期待しています。キューの先頭を期限切れのデータにするため、compareToメソッドで実装する必要があります。各要素の有効期限で並べ替えます、次のように:
(int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));

このようにして、期限切れの要素をより早くチームのトップにランク付けすることができます。

データを追加する場合、compareToメソッドはキュー内の要素の並べ替えにのみ使用されることがわかります。次に、データをフェッチするときの操作方法を見てみましょう。

4データを取る

データをフェッチするときに、要素の有効期限が切れていることがわかった場合、データを取り出すことができます。期限切れの要素がない場合、スレッドは常にブロックされます。例として、コアソースコードを見てみましょう:

for (;;) {
    // 从队头中拿数据出来
    E first = q.peek();
    // 如果为空,说明队列中,没有数据,阻塞住
    if (first == null)
        available.await();
    else {
        // 获取队头数据的过期时间
        long delay = first.getDelay(NANOSECONDS);
        // 如果过期了,直接返回队头数据
        if (delay <= 0)
            return q.poll();
        // 引用置为 null ,便于 gc,这样可以让线程等待时,回收 first 变量
        first = null;
        // leader 不为空的话,表示当前队列元素之前已经被设置过阻塞时间了
        // 直接阻塞当前线程等待。
        if (leader != null)
            available.await();
        else {
          // 之前没有设置过阻塞时间,按照一定的时间进行阻塞
            Thread thisThread = Thread.currentThread();
            leader = thisThread;
            try {
                // 进行阻塞
                available.awaitNanos(delay);
            } finally {
                if (leader == thisThread)
                    leader = null;
            }
        }
    }
}

ブロッキング待機機能は、下部のロック機能を使用していることがわかります。これについては、後の章で説明します。

上記のtakeメソッドは無期限にブロックされ、チームヘッドの有効期限が切れるまで戻りません。無期限にブロックしたくない場合は、ポーリングメソッドを試してタイムアウト期間を設定できます。チームヘッドエレメントがタイムアウト時間内に期限切れにならない場合は、 nullを返します。

5まとめ

DelayQueueは非常に興味深いキューです。最下層は並べ替えとタイムアウトブロックを使用して遅延キューを実装します。並べ替えはPriorityQueue並べ替え機能を使用します。タイムアウトブロックはロック待機機能を使用します。DelayQueueは実際に遅延実行シナリオに対応していることがわかります。これは既存のAPIに基づいてカプセル化されています。私たちは作業でこのアイデアを学び、既存の関数を可能な限り再利用して開発のワークロードを削減できます。

公開された40元の記事 ウォンの賞賛1 ビュー4984

おすすめ

転載: blog.csdn.net/aha_jasper/article/details/105525753