Programación concurrente de Java: comprensión profunda del bloqueo de giro

1. ¿Qué es un bloqueo giratorio?

Spinlock: se refiere a cuando un subproceso está adquiriendo un bloqueo, si el bloqueo ha sido adquirido por otros subprocesos, entonces el subproceso esperará en un bucle y luego determinará constantemente si el bloqueo se puede adquirir con éxito, hasta que se adquiere el bloqueo. Sal del bucle.
El hilo que adquiere el bloqueo ha estado activo, pero no ha realizado ninguna tarea efectiva. El uso de este bloqueo provocará una espera ocupada .

2. ¿Cómo implementa Java el bloqueo de giro?

Echemos un vistazo a un ejemplo de implementación de un bloqueo de giro. El paquete java.util.concurrent proporciona muchas clases para la programación concurrente. El uso de estas clases tendrá un mejor rendimiento en una máquina con CPU de varios núcleos. La razón principal es que se utilizan la mayoría de estas clases. (Modo de reintento por falla) Bloqueo optimista en lugar de bloqueo pesimista en modo sincronizado.


class spinlock {
    private AtomicReference<Thread> cas;
    spinlock(AtomicReference<Thread> cas){
        this.cas = cas;
    }
    public void lock() {
        Thread current = Thread.currentThread();
        // 利用CAS
        while (!cas.compareAndSet(null, current)) { //为什么预期是null??
            // DO nothing
            System.out.println("I am spinning");
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        cas.compareAndSet(current, null);
    }
}

El CAS utilizado por el método lock (). Cuando el primer hilo A adquiere el bloqueo, puede adquirirlo con éxito sin entrar en el bucle while. Si el hilo A no libera el bloqueo en este momento, otro hilo B adquiere el bloqueo nuevamente. Dado que el CAS no está satisfecho, entrará en el ciclo while y continuará juzgando si el CAS está satisfecho hasta que el hilo A llame al método de desbloqueo para liberar el bloqueo.

Código de verificación Spinlock

package ddx.多线程;

import java.util.concurrent.atomic.AtomicReference;

public class 自旋锁 {
    public static void main(String[] args) {
        AtomicReference<Thread> cas = new AtomicReference<Thread>();
        Thread thread1 = new Thread(new Task(cas));
        Thread thread2 = new Thread(new Task(cas));
        thread1.start();
        thread2.start();
    }

}

//自旋锁验证
class Task implements Runnable {
    private AtomicReference<Thread> cas;
    private spinlock slock ;

    public Task(AtomicReference<Thread> cas) {
        this.cas = cas;
        this.slock = new spinlock(cas);
    }

    @Override
    public void run() {
        slock.lock(); //上锁
        for (int i = 0; i < 10; i++) {
            //Thread.yield();
            System.out.println(i);
        }
        slock.unlock();
    }
}

Se creó un cas de bloqueo de giro a través de la clase AtomicReference anterior, y luego se crearon y ejecutaron dos subprocesos por separado. Los resultados son los siguientes:


0
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
I am spin
1
I am spin
I am spin
I am spin
I am spin
I am spin
2
3
4
5
6
7
8
9
I am spin
0
1
2
3
4
5
6
7
8
9

Programación concurrente de Java: comprensión profunda del bloqueo de giro

Mediante el análisis de los resultados de salida, podemos saber que, en primer lugar, asumir que el hilo ha adquirido el bloqueo al ejecutar el método de bloqueo.

cas.compareAndSet (nulo, actual)

Cambie la referencia a la referencia del hilo uno, omita el ciclo while y ejecute la función de impresión

El segundo hilo también ingresa al método de bloqueo en este momento, ¡y encontró ese valor esperado! = actualizar valor, luego ingrese el ciclo while e imprima

estoy dando vueltas. Se puede concluir a partir de las siguientes letras rojas que un subproceso en Java no siempre ocupa el intervalo de tiempo de la CPU y se ha ejecutado todo el tiempo, sino que utiliza la programación preventiva, por lo que los dos subprocesos anteriores alternan el fenómeno de ejecución

La realización de los subprocesos de Java se asigna a los subprocesos ligeros del sistema. Los subprocesos ligeros tienen los subprocesos del kernel correspondientes del sistema. La programación de los subprocesos del kernel es programada por el programador del sistema, por lo que el método de programación de subprocesos de Java depende de la programación del kernel del sistema Simplemente sucede que las implementaciones de subprocesos de los sistemas operativos convencionales son preventivas.

3. Problemas con los candados de giro

El uso de bloqueos de giro tiene los siguientes problemas:
1. Si un subproceso mantiene el bloqueo durante demasiado tiempo, hará que otros subprocesos que esperan adquirir el bloqueo ingresen al bucle en espera y consuman CPU. El uso inadecuado provocará un uso extremadamente alto de la CPU.
2. El bloqueo de giro implementado por Java arriba no es justo, es decir, no puede satisfacer el subproceso con el tiempo de espera más largo para obtener el bloqueo primero. Los bloqueos injustos tendrán problemas de "falta de hilo".

4. Ventajas del bloqueo de giro

  1. El bloqueo de giro no hará que el estado del hilo cambie, y siempre estará en el estado de usuario, es decir, el hilo siempre está activo; no hará que el hilo entre en el estado de bloqueo, lo que reduce el cambio de contexto innecesario, y la velocidad de ejecución es rápida
  2. El bloqueo sin giro ingresa al estado de bloqueo cuando no se adquiere el bloqueo y, por lo tanto, ingresa al estado del núcleo.Cuando se adquiere el bloqueo, debe restaurarse desde el estado del núcleo y requiere un cambio de contexto del hilo. (Una vez que el hilo está bloqueado, ingresa al estado de programación del kernel (Linux). Esto hará que el sistema cambie entre el modo de usuario y el modo de kernel, lo que afectará seriamente el rendimiento del bloqueo)

5. Bloqueo de giro reentrante y bloqueo de giro no reentrante

Un análisis cuidadoso del código al principio del artículo muestra que no admite la reentrada, es decir, cuando un hilo ha adquirido el bloqueo por primera vez, lo adquirirá nuevamente antes de que se libere el bloqueo. No se puede obtener con éxito la segunda vez. Dado que CAS no se satisface, la segunda adquisición entrará en el bucle while y esperará, y si es un bloqueo reentrante, la segunda adquisición debería ser exitosa.
Además, incluso si se puede adquirir con éxito la segunda vez, cuando se libera el bloqueo por primera vez, se liberará el bloqueo adquirido por segunda vez, lo cual no es razonable.

Por ejemplo, cambie el código a lo siguiente:

@Override
    public void run() {
        slock.lock(); //上锁
        slock.lock(); //再次获取自己的锁!由于不可重入,则会陷入循环
        for (int i = 0; i < 10; i++) {
            //Thread.yield();
            System.out.println(i);
        }
        slock.unlock();
    }

El resultado de la ejecución se imprimirá indefinidamente, ¡cayendo en un bucle sin fin! 

Para lograr bloqueos reentrantes, necesitamos introducir un contador para registrar el número de subprocesos que adquieren el bloqueo.


public class ReentrantSpinLock {
    private AtomicReference<Thread> cas = new AtomicReference<Thread>();
    private int count;
    public void lock() {
        Thread current = Thread.currentThread();
        if (current == cas.get()) { // 如果当前线程已经获取到了锁,线程数增加一,然后返回
            count++;
            return;
        }
        // 如果没获取到锁,则通过CAS自旋
        while (!cas.compareAndSet(null, current)) {
            // DO nothing
        }
    }
    public void unlock() {
        Thread cur = Thread.currentThread();
        if (cur == cas.get()) {
            if (count > 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟
                count--;
            } else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。
                cas.compareAndSet(cur, null);
            }
        }
    }
}

De la misma manera, el método de bloqueo primero determinará si el hilo actual ha adquirido el bloqueo, y luego aumentará el recuento en uno, volverá a entrar y luego regresará directamente. El método de desbloqueo primero determinará si el hilo actual ha obtenido el bloqueo. Si lo hace, primero determinará el contador, seguirá disminuyendo en uno y seguirá desbloqueando.

 Verificación del código de spinlock reentrante


//可重入自旋锁验证
class Task1 implements Runnable{
    private AtomicReference<Thread> cas;
    private ReentrantSpinLock slock ;

    public Task1(AtomicReference<Thread> cas) {
        this.cas = cas;
        this.slock = new ReentrantSpinLock(cas);
    }

    @Override
    public void run() {
        slock.lock(); //上锁
        slock.lock(); //再次获取自己的锁!没问题!
        for (int i = 0; i < 10; i++) {
            //Thread.yield();
            System.out.println(i);
        }
        slock.unlock(); //释放一层,但此时count为1,不为零,导致另一个线程依然处于忙循环状态,所以加锁和解锁一定要对应上,避免出现另一个线程永远拿不到锁的情况
        slock.unlock();
    }
}

6. Las similitudes y diferencias entre los bloqueos de giro y los bloqueos de mutex

  • Tanto los bloqueos de giro como los bloqueos de exclusión mutua son mecanismos para proteger el uso compartido de recursos.
  • Ya sea un bloqueo giratorio o un bloqueo mutex, puede haber como máximo un soporte en cualquier momento.
  • Si el subproceso que adquiere el bloqueo de mutex ya está ocupado, el subproceso entrará en estado de suspensión; el subproceso que adquiere el bloqueo de giro no se suspenderá, pero esperará a que el bloqueo se libere en un bucle.

7. Resumen

  • Bloqueo de giro: cuando un hilo adquiere un bloqueo, si el bloqueo está retenido por otro hilo, el hilo actual esperará en un bucle hasta que se adquiera el bloqueo.
  • Durante el período de espera del bloqueo de giro, el estado del hilo no cambiará y el hilo siempre estará en modo de usuario y activo.
  • Si el bloqueo de giro mantiene el bloqueo durante demasiado tiempo, hará que otros subprocesos que esperan adquirir el bloqueo agoten la CPU.
  • Spinlock en sí no puede garantizar la equidad, ni puede garantizar la reentrada.
  • Basado en cerraduras de giro, se puede realizar una cerradura justa y reentrante

fin

Este es el final de este artículo. Gracias por ver a los últimos amigos. Todos se ven al final. Solo dame un pulgar hacia arriba antes de irte. Si hay algo mal, corrígeme.

Supongo que te gusta

Origin blog.51cto.com/14969174/2542996
Recomendado
Clasificación