Análisis de concurrencia en profundidad del principio de implementación de ReentrantLock

1. Introducción

  Antes de pasar algún tiempo para estudiar un componente importante bajo el paquete concurrente: el sincronizador de cola abstracto AQS, en el paquete concurrente , muchas clases se basan en él, incluidos Javalos bloqueos de uso común ReentrantLock. Conociendo el AQSprincipio de realización, la realización de la comprensión ReentrantLockes muy simple, porque su función de bloqueo se realiza mediante la AQSrealización, y su trabajo es solo reescribir algunos AQSde los métodos relacionados, y usar el método de plantilla para agregar La cerradura está desbloqueada. El blog de hoy analizará ReentrantLockla implementación desde la perspectiva del código fuente .


Segundo, el texto.

2.1 Sincronizador de cola abstracto AQS

  Antes de hablar ReentrantLock, debemos mencionarlo primero AQS. AQSNombre completo Abstract Queue Synchronizer (AbstractQuenedSynchronizer), es un marco básico que se puede utilizar para lograr la sincronización de subprocesos. Por supuesto, no es Springun marco tal como lo entendemos , es una clase, el nombre de la clase es AbstractQuenedSynchronizer, si queremos implementar un bloqueo o un componente de sincronización similar que pueda completar la sincronización de subprocesos, puede usarse AQSpara lograrlo, porque encapsula los subprocesos De forma síncrona, lo usamos en nuestra propia clase, podemos lograr fácilmente un bloqueo propio.

  AQSLa implementación de es relativamente complicada y no se puede aclarar en pocas palabras.He escrito anteriormente un AQSblog dedicado al análisis de los principios de implementación: concurrencia : el principio de implementación del sincronizador de cola abstracto AQS .

  Antes de leer el siguiente contenido, asegúrese de aprender el principio de implementación de AQS , ya que ReentrantLockla implementación es muy simple, depende completamente de él AQS, por lo que mis siguientes descripciones se AQSbasan en la comprensión . Puede leer el blog recomendado arriba, o puede verificar la información relevante usted mismo.


2.2 El principio de realización de ReentrantLock

  Primero, introduzcamos brevemente ReentrantLockel principio de implementación, para que podamos leer su código fuente a continuación. He dicho antes, ReentrantLocken base a la AQSaplicación AQSdel método de plantillaacquire , releaseetc., han dado cuenta de las operaciones de bloqueo y desbloqueo, y sólo lo utiliza métodos llamadas a métodos de clase necesitan para anular estas plantillas, por ejemplo tryAcquire, tryReleasey así sucesivamente, estos métodos mediante la modificación de AQSla Estado sincronizado statepara bloquear y desbloquear. AQSEl estado de sincronización statees un inttipo de valor. De acuerdo con diferentes valores, puede determinar el estado de bloqueo actual. La modificación de este valor es la forma de bloquear y desbloquear.

  El uso de AQSenfoque general clase interna en la forma de herencia AQS, ReentrantLockes así que se dio cuenta, en su interior, con tres AQSde las clases derivadas:

  1. Primero, el nombre de la primera clase derivada se llama Sync , que es una clase abstracta que hereda directamente de ella AQS, que define algunos métodos comunes;
  2. La segunda clase derivada se llama NonfairSync , que hereda de ella Synce implementa un bloqueo injusto ;
  3. La tercera clase derivada se llama FairSync , que también se hereda de ella Synce implementa un bloqueo justo ;

  ReentrantLockEs a través de NonfairSyncobjetos u FairSyncobjetos para garantizar la sincronización del hilo. Los métodos escritos en estas tres clases son en realidad formas de modificar el estado de sincronización. Cuando stateel valor del 0tiempo, y no representan el hilo actual para adquirir el bloqueo, y cada uno obtener un bloqueo, stateel valor será +1liberar un bloqueo stateen -1. Echemos un vistazo más de cerca al código fuente de estas tres categorías.


2.3 Análisis de código fuente de sincronización

  Echemos un vistazo directamente a Synclos métodos en la clase. Hay muchos métodos en la clase Sync. Solo pensaré en algunos de los más importantes:

abstract static class Sync extends AbstractQueuedSynchronizer {
	/** 定义一个加锁的抽象方法,由子类实现 */
    abstract void lock();

    /**
     * 此方法的作用是以非公平的方式尝试获取一次锁,获取成功则返回true,否则返回false;
     * 需要注意,AQS的获取锁,实际上就是修改同步状态state的值。
     * 这里有个疑惑,既然是非公平地获取锁,那这个方法为什么不写在NonfairSync类中?
     * 因为ReentrantLock有一个方法tryLock,即尝试获取一次锁,调用tryLock方法时,
     * 无论使用的是公平锁还是非公平锁,实际上都需要尝试获取一次锁,也就是调用这个方法,
     * 所以这个方法定义在了父类Sync中
    */
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前正在运行的线程
        final Thread current = Thread.currentThread();
        // 获取同步状态state的值,state定义在父类AQS中,
        int c = getState();
        // 若当前state的值为0,表示还没有线程获取锁,于是当前线程可以尝试获取锁
        if (c == 0) {
            // compareAndSetState方法通过CAS的方式修改state的值,
            // 实际上就是让state从0变为1,因为acquires的值就是1,
            // 每次有线程获取了锁时,同步状态就+1
            if (compareAndSetState(0, acquires)) {
                // 若compareAndSetState方法返回true,表示修改state成功,
                // 则调用setExclusiveOwnerThread方法将当前线程记录为占用锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 若以上c == 0不满足,则表示已经有线程获取锁了,
        // 于是调用getExclusiveOwnerThread方法获取当前正在占用锁的线程,
        // 然后和当前线程比较,若当前线程就是占用锁的线程,则当前线程不会被阻塞,
        // 可以再次获取锁,从这里可以看出,ReentrantLock是一个可重入锁
        else if (current == getExclusiveOwnerThread()) {
            // 计算当前线程获取锁后,state的值应该是多少,实际上就是让state + 1
            int nextc = c + acquires;
            // 如果nextc小于0,则保存,因为理论上同步状态是不可能小于0的
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 使用上面计算出的nextc更新state的值,这里需要注意一点
            // setState不像compareAndSetState方法,setState方法并不保证操作的原子性
            // 这里不需要保证原子性,因为这里线程已经获取了锁,所以不会有其他线程进行操作
            setState(nextc);
            // 返回true表示加锁成功
            return true;
        }
        // 若以上条件均不满足,表示有其他线程获取了锁,当前线程获取锁失败
        return false;
    }

    
    /**
     * 此方法是的作用是尝试释放锁,其实也就是让state的值-1
     * 这个方法是一个通用的方法,不论使用的是公平锁还是非公平锁
     * 释放锁时都是调用此方法修改同步状态
     */
    protected final boolean tryRelease(int releases) {
        // getState方法获取state的值,并与参数相减,计算释放锁后state的值
        // 在ReentrantLock中其实就是-1
        int c = getState() - releases;
		// 判断当前线程是不是占用锁的线程,若不是则抛出异常
        // 因为只有占用了锁的线程才可以释放锁
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        
        // 变量free用来标记锁释放真正的被释放,因为ReentranLock是一个重入锁
        // 获取锁的线程可以多次获取锁,只有每一次获取都释放,锁才是真正的释放
        boolean free = false;
        // 判断c的值是否是0,只有c的值是0,也就是state的值为0时
        // 才说明当前的线程在这次释放锁后,锁真正的处于没有被使用的状态
        if (c == 0) {
            // 若满足此条件,则free标记为true,表示锁真的被释放了
            free = true;
            // 然后标记当前占用锁的线程为null,也就是没有线程占用锁
            setExclusiveOwnerThread(null);
        }
        // 将c的值更新同步状态state
        setState(c);
        return free;
    }
	
    /** 此方法判断当前线程是不是获取锁的线程 */
    protected final boolean isHeldExclusively() {
        // getExclusiveOwnerThread方法返回当前正在占用锁的线程
        // 于当前的运行的线程进行比较
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
}

  Lo anterior es Syncla implementación de la clase. De hecho Sync, no solo existen los métodos anteriores, sino que los métodos restantes son todos fragmentos, que ReentrantLockno son muy útiles para nuestra comprensión , por lo que no los enumeraré aquí. De la implementación del método anterior, podemos conocer la siguiente información: la forma en que el subproceso adquiere el bloqueo es en realidad aumentar el valor del estado del estado de sincronización, y la forma de liberar el bloqueo es reducir el valor del estado; y ReentrantLock implementa la reentrada Bloqueo, el hilo que ha adquirido el bloqueo puede adquirir el bloqueo nuevamente sin obstáculos, y el valor del estado puede continuar aumentando. Cuando se libera el bloqueo, el bloqueo se libera realmente solo cuando el valor del estado disminuye a 0 .


2.4 Análisis del código fuente de NonfairSync

Aquí nos fijamos en la segunda clase interna NonfairSyncque implementa un no justas cerraduras .

/**
 * 此类继承自Sync,它实现的是非公平锁
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    
    /**
     * 在父类Sync中定义的lock方法,在子类中实现
     */
    final void lock() {
        // 调用compareAndSetState方法,企图使用CAS机制将state的值从0修改为1
        // 若state的值不为0,表示锁已经被其他线程获取了,则当前线程将获取锁失败
        // 或者state的值一开始是0,但是在当前线程修改的过程中,被其他线程修改了,
        // 也会返回false。若修改state失败,则就需要执行下面的acquire方法
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        // acquire方法是AQS定义的模板方法,这个方法会调用tryAcquire尝试获取锁,
        // 而tryAcquire方法是由子类实现的,也就是下面那个方法;
        // 若调用tryAcquire获取锁失败,则AQS会将线程封装成一个节点Node
        // 丢入AQS的同步队列中排队(这个具体实现请参考AQS的实现博客)
        // 归根到底,这个方法就是让线程获取锁,不断尝试,直到成功为止.
        // 注意这里传入的参数是1,表示加锁实际上就是让state的值+1
        else
            acquire(1);
    }

    
    /** 
     * 此方法tryAcquire在AQS的模板方法中被调用,它的作用就是尝试获取一次锁,
     * 也就是尝试修改一次同步状态state;
     * 不同的实现类根据不同的需求重写tryAcquire方法,就可以按自己的意愿控制加锁的方式
     * AQS就是通过这种方式来提供给其他类使用的
     */
    protected final boolean tryAcquire(int acquires) {
        // 此处直接调用了父类Sync中,非公平地获取一次锁的nonfairTryAcquire方法
        return nonfairTryAcquire(acquires);
    }
}

  Lo anterior es el NonfairSynccódigo completo de la clase, y no se ha eliminado, se puede ver que es muy corto. Implementó el método Syncdefinido en la clase locky reescribió el tryAcquiremétodo al mismo tiempo , para AQSque acquirellame el método de plantilla , y tryAcquirela implementación es solo Syncel nonfairTryAcquiremétodo llamado . Para ayudar a entender, echamos un vistazo a AQSla acquirecódigo para el método es:

public final void acquire(int arg) {
    // 这里首先调用tryAcquire方法尝试获取一次锁,在AQS中这个方法没有实现,
    // 而具体实现是在子类中,也就是调用的是NonfairSync的tryAcquire方法,
    // 若方法返回true,表示成功获取到锁,于是后面代码都不会执行了,
    // 否则,将先执行addWaiter方法,这个方法的作用是将当前线程封装成为一个Node节点,
    // 加入到AQS的同步队列的尾部,同时将返回这个Node节点,并传入acquireQueued方法
    // acquireQueued方法的作用就是让当前线程阻塞,直到成功获取到锁才会从这个方法返回
    // acquireQueued会返回这个线程在等待的过程中是否被中断,若被中断,
    // 则调用selfInterrupt方法真正执行中断。
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

  ¿Por qué NonfairSynces injusto el bloqueo? Podemos ver que en NonfairSyncel lockmétodo, antes de que un subproceso intente adquirir el bloqueo, no determina si hay un subproceso esperando adquirir el bloqueo antes que él, sino que intenta llamar directamente al compareAndSetStatemétodo para adquirir el bloqueo. Si la adquisición falla, ingrese el acquiremétodo, En este método, se volverá a llamar al método para tryAcquireadquirir el bloqueo. En este momento, si la adquisición falla nuevamente, será puesta en cola en la cola de sincronización. Durante este proceso, se insertan dos colas, por lo que NonfairSynces un bloqueo injusto.


2.5 Análisis del código fuente de FairSync

  Echemos un vistazo a la última clase interna FairSync, que implementa bloqueos justos , es decir, los subprocesos adquieren bloqueos en el orden de orden de llegada, sin intervenir:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    /** 实现父类的lock方法,加锁 */
    final void lock() {
        // 直接调用AQS的模板方法acquire进行加锁,调用这个方法后,线程若没有获取锁
        // 则会被阻塞,直到获取了锁后才会返回。这里需要注意一点,和NonfairSync中的lock不同
        // 这里直接调用acquire,而不会先调用一次compareAndSetState方法获取锁
        // 因为FairSync是公平锁,所以不会执行这种插队的操作.
        // 注意这里传入的参数是1,表示加锁实际上就是让state的值+1
        acquire(1);
    }

    /** 
     * 和NonfairSync一样,重写AQS的tryAcquire方法,若使用的是FairSync,
     * 则acquire中将调用此tryAcquire方法,尝试获取一次锁
     */
    protected final boolean tryAcquire(int acquires) {
        // 首先获取当前正在执行的线程
        final Thread current = Thread.currentThread();
        // 记录同步状态
        int c = getState();
        // 若state的值为0,表示现在没有线程占用了锁,于是当前线程可以尝试获取锁
        if (c == 0) {
            // 尝试获取锁前,先调用hasQueuedPredecessors方法,这个方法是判断
            // 是否有其他线程正在排队尝试获取锁,若有,方法将返回true,那为了公平性,
            // 当前线程不能获取锁,于是直接结束,否则调用compareAndSetState修改state
            // 若修改成功,调用setExclusiveOwnerThread方法将自己设置为当前占用锁的线程
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 若state不等于0,则表示当前锁已经被线程占用,那此处判断占用锁的线程是否是自己
        // 若是,则当前线程可以再次获取锁,因为ReentrantLock实现的是可重入锁,
        else if (current == getExclusiveOwnerThread()) {
            // 计算当前线程再次获取锁后,state的值将变为多少,此处实际上就是 + 1
            int nextc = c + acquires;
            // 理论上state的值不可能小于0,于是若小于0,就报错
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            // 修改state的值为上面计算的新值,此处不需要CAS操作保证原子性,
            // 因为当前线程已经获取了锁,那其他线程就不能修改state,所以这里可以放心修改
            setState(nextc);
            return true;
        }
        // 若以上条件均不满足,表示有其他线程占用了锁,则直接返回false
        return false;
    }
}

  FairSyncLa implementación es relativamente simple. Vale la pena señalar que debido a que FairSync implementa un bloqueo equitativo, antes de que un subproceso adquiera un bloqueo, primero determinará si hay un subproceso que está tratando de adquirir el bloqueo antes de que se coloque en cola. Después de esos hilos .


2.6 Atributos del miembro ReentrantLock y método de construcción

  Después de leer la clase interna, echemos ReentrantLockun vistazo formal a cómo funciona. Primero , echemos un vistazo a sus propiedades miembro y métodos de construcción:

/** 记录使用的锁对象 */
private final Sync sync;

/** 默认构造方法,初始化锁对象,默认使用非公平锁 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/** 参数为boolean类型的构造方法,若为false,使用非公平锁,否则使用公平锁 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2.7 Bloqueo y desbloqueo de ReentrantLock

  Permítanme echar un vistazo a ReentrantLocklas dos operaciones más importantes, bloquear y desbloquear.

(1) El método de adquisición de la cerradura

/** 
 * 此方法让当前线程获取锁,若获取失败,线程将阻塞,直到获取成功为止 
 * 此方法不会响应中断,也就是在没有获取锁前,将无法退出
 */
public void lock() {
    // 直接调用锁对象的lock方法,也就是之前分析的内部类中的lock方法
    sync.lock();
}

/**
 * 此方法获取锁,和上面的方法类似,唯一的不同就是,调用这个方法获取锁时,
 * 若线程被阻塞,可以响应中断
 */
public void lockInterruptibly() throws InterruptedException {
    // 调用sync对象的acquireInterruptibly方法,这个方法定义在AQS中,
    // 也是AQS提供给子类的一个模板方法,内部也是通过tryAcquire获取锁,
    // 若获取失败,线程将被阻塞,但是此方法会检测中断信号,
    // 若检测到中断,将通过抛出异常的方式退出阻塞
    // 关于这个方法的具体实现,可以去参考AQS的相关博客,此处就不展开描述了
    sync.acquireInterruptibly(1);
}


/** 
 * 调用此方法尝试获取一次锁,不论成功失败,都会直接返回
 */
public boolean tryLock() {
    // 此处直接调用Sync类中的nonfairTryAcquire方法,
    // 这也就是为什么nonfairTryAcquire定义在父类Sync中,
    // 因为不论是使用公平锁还是非公平锁,都需要在此处调用这个方法
    return sync.nonfairTryAcquire(1);
}

(2) Si se implementa el método de bloqueo

/**
 * 此方法用来释放锁
 */
public void unlock() {
    // 此处调用的是AQS的release方法,这个方法也是AQS提供的一个模板方法,
    // 在这个方法中,将调用子类重写的tryRelease方法尝试释放锁,若释放成功
    // 则会唤醒等待队列中的下一个线程,让它停止阻塞,开始尝试获取锁,
    // 关于这个方法的具体实现,可以参考我之前推荐的AQS源码分析博客。
    // 这里需要注意,传入的参数是1,表明释放锁实际上就是让state的值-1
    sync.release(1);
}

  Lo anterior es ReentrantLockel método de bloqueo y desbloqueo, que es inesperado y muy simple. Cada método tiene solo una oración y llama AQSal método de plantilla proporcionado en la clase. Este es AQSel beneficio, AQSencapsulando el código de sincronización de subprocesos, solo necesitamos usarlo en la clase, podemos implementar fácilmente un bloqueo. Es por eso que dije antes que antes de leer ReentrantLock, debes estudiar primero AQSy entender AQS, ReentrantLockno hay dificultad para entender .

  Los anteriores ReentrantLockson los métodos clave. Además de estos métodos, hay muchos otros métodos, pero esos métodos no son críticos, y la implementación es muy simple. Básicamente, es solo una oración de código. Puede leer el código fuente usted mismo. No los enumeraré uno por uno.


3. Resumen

  Después del análisis anterior, nos encontramos con que ReentrantLockel principio de realización es muy sencilla, ya que se basa en la AQSrealización de la complejidad se encapsula en el AQSmedio, ReentrantLocksólo a sus usuarios, por lo que el aprendizaje ReentrantLockes en realidad el aprendizaje AQS. AQSEs Javaun componente importante en la concurrencia, y muchas clases se implementan en base a él, como las que se usan con mucha frecuencia CountDownLatch. AQSTambién es una pregunta común en la entrevista, por lo que debe estudiarla detenidamente. Aquí nuevamente recomiendo el AQSblog de análisis que escribí : Concurrencia : el principio de realización del sincronizador de cola abstracto AQS .


4. Referencia

  • Código fuente JDK1.8

Supongo que te gusta

Origin www.cnblogs.com/tuyang1129/p/12689122.html
Recomendado
Clasificación