Análisis del código fuente de Java y preguntas de la entrevista: análisis del código fuente de DelayQueue

Esta serie de blog relacionado, Mu columna de referencia de clase Java de código fuente y del sistema fabricantes entrevistador sucinta Zhenti
por debajo de esta columna es la dirección de GitHub:
Fuente resolvió: https://github.com/luanqiu/java8
artículo Demostración: HTTPS: // GitHub. com / luanqiu / java8_demo los
compañeros de clase pueden verlo si es necesario)

Análisis del código fuente de Java y preguntas de la entrevista: análisis del código fuente de DelayQueue

Observaciones introductorias
La cola de bloqueo que mencionamos anteriormente se ejecuta inmediatamente cuando hay suficientes recursos. La cola de la que hablamos en este capítulo es especial, es una especie de cola retrasada, lo que significa una ejecución retrasada, y puede establecer cuánto tiempo retrasará la ejecución, por ejemplo, después de 5 segundos, y luego ejecutarla. Reconciliación y demás.

1 diseño general

DelayQueue La parte inferior de la cola de retraso utiliza la capacidad de bloqueo. Por ejemplo, si desea retrasar la ejecución durante 5 segundos hacia atrás en el momento actual, entonces el hilo actual dormirá durante 5 segundos. Cuando el hilo se despierta después de 5 segundos, si se puede obtener el recurso, el hilo Se puede ejecutar de inmediato. Parece ser simple en principio, pero la implementación interna es muy complicada. Hay muchas dificultades. Por ejemplo, cuando no hay suficientes recursos en ejecución y se despiertan múltiples hilos al mismo tiempo, ¿cómo hacer cola y esperar? Por ejemplo, ¿cuándo se bloquea? ¿Cuándo comienza la ejecución, etc.? A continuación, veremos cómo se implementa desde la perspectiva del código fuente.

1.1 Apuntes de clase

Las anotaciones de clase son relativamente simples, solo se mencionan tres conceptos:

  1. Los elementos en la cola se ejecutarán cuando caduquen, cuanto más cerca de la cabecera de la cola, antes caducarán;
  2. Los elementos no vencidos no se pueden tomar;
  3. Los elementos vacíos no están permitidos.

Estos tres conceptos son en realidad tres problemas. A continuación veremos cómo se implementan estos tres puntos.

1.2 Diagrama de clases

El diagrama de clases de DelayQueue es el mismo que la cola anterior, sin mencionar que la clave es que la clase DelayQueue es genérica, de la siguiente manera:

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

Los genéricos pueden ver que los elementos en DelayQueue deben ser una subclase de Retraso. Retraso es una interfaz clave que expresa la capacidad de retrasar. Hereda la interfaz Comparable y define cuánto tiempo expira el tiempo restante, de la siguiente manera:

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

En otras palabras, los elementos en la cola DelayQueue deben implementar la interfaz Retrasada y la interfaz Comparable, y anular el método getDelay y el método compareTo. De lo contrario, al compilar, el compilador nos recordará que el elemento debe forzar la interfaz Retrasada.

Además, DelayQueue también utiliza muchas funciones de la cola PriorityQueue, que es muy similar a la cola SynchronousQueue, se reutiliza mucha lógica de otras clases básicas, el ejemplo de código es el siguiente:
Inserte la descripción de la imagen aquí
PriorityQueue Chinese se llama cola prioritaria, el papel aquí es Priorice de acuerdo con el tiempo de caducidad, de modo que el primero en caducar pueda ejecutarse primero, para alcanzar el primer punto en el comentario de la clase.

La idea de reutilizar aquí es bastante importante. A menudo encontramos esta idea en el código fuente, como la capacidad de LinkedHashMap para reutilizar HashMap, la capacidad de Set para reutilizar Map y la capacidad de DelayQueue para reutilizar PriorityQueue aquí. . Para resumir, qué debe hacer si desea reutilizar:

  1. Es necesario abstraer las funciones que se pueden reutilizar tanto como sea posible y abrir lugares extensibles.Por ejemplo, en el método de operar matrices, HashMap abre muchos métodos a LinkedHashMap después del comienzo, lo cual es conveniente para LinkedHashMap para ordenar, eliminar, etc.
  2. Use la combinación o herencia para reutilizar, como la herencia utilizada por LinkedHashMap, la combinación utilizada por Set y DelayQueue, el significado de la combinación es confiar en clases reutilizables.

2 Demo

Para facilitar la comprensión de todos, escribí una demostración demo que demuestra:

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

El propósito de escribir este código es principalmente para demostrar el ejemplo de ejecución retrasada. Nuestra idea general es:

  1. Los elementos de la cola recién creada, como DelayedDTO, deben implementar la interfaz Demorada. En el método getDelay, hemos implementado el método cuánto tiempo queda hasta el tiempo de caducidad.
  2. Defina los productores y consumidores de los elementos de la cola, correspondientes al Producto y al Consumidor en el código.
  3. Inicializar y gestionar productores y consumidores, correspondiente a nuestro método principal.

Aunque esto es solo una demostración simple, en el trabajo real, usamos DelayQueue es básicamente esta idea, pero cuando la escritura del código sea más general y completa, echemos un vistazo a cómo DelayQueue implementa el uso y el uso.

3 poner datos

Tomemos put como ejemplo. Put llama al método de oferta. El código fuente de la oferta es el siguiente:

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();
    }
}

Se puede ver que el método de oferta de PriorityQueue se usa realmente en la parte inferior, echemos un vistazo:

// 新增元素
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;
}

Como puede ver, el método de oferta de PriorityQueue hace principalmente tres cosas:

  1. Juzgar los elementos recién agregados;
  2. Expande la cola, la estrategia de expansión es muy similar a la estrategia de expansión establecida;
  3. Ordenando de acuerdo con el método de comparación de elementos, esperamos que los resultados finales de clasificación sean de pequeño a grande, porque queremos que el encabezado de la cola sean datos caducados, necesitamos implementar en el método de comparación: ordenar por el tiempo de caducidad de cada elemento De la siguiente manera:
(int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));

De esta manera, antes los elementos caducados se pueden clasificar al frente del equipo.

Se puede ver que al agregar datos, el método compareTo solo se usa para ordenar los elementos en la cola. A continuación, echemos un vistazo a cómo operar cuando se obtienen datos.

4 Toma los datos

Al buscar datos, si encuentra que el tiempo de vencimiento de un elemento ha expirado, puede extraer los datos. Si no hay ningún elemento expirado, el hilo siempre se bloqueará. Tomemos como ejemplo para ver el código fuente central:

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;
            }
        }
    }
}

Se puede ver que la función de bloqueo de espera utiliza la capacidad de bloqueo en la parte inferior, de lo que hablaremos en capítulos posteriores.

El método de toma demostrado anteriormente se bloqueará indefinidamente y no volverá hasta que haya expirado el tiempo de expiración del jefe del equipo. Si no desea bloquear indefinidamente, puede probar el método de sondeo y establecer un período de tiempo de espera. Si el elemento de la cabeza del equipo no ha expirado dentro del tiempo de espera, lo hará Regreso nulo.

5 Resumen

DelayQueue es una cola muy interesante. La capa inferior utiliza la clasificación y el bloqueo del tiempo de espera para implementar una cola de retraso. La clasificación utiliza la capacidad de clasificación PriorityQueue. El bloqueo del tiempo de espera utiliza la capacidad de espera de bloqueo. Se puede ver que DelayQueue realmente cumple con el escenario de ejecución de retraso. Está encapsulado sobre la base de la API existente. Podemos aprender esta idea en nuestro trabajo y reutilizar las funciones existentes tanto como sea posible para reducir la carga de trabajo de desarrollo.

Publicado 40 artículos originales · ganado elogios 1 · vistas 4984

Supongo que te gusta

Origin blog.csdn.net/aha_jasper/article/details/105525753
Recomendado
Clasificación