La teoría del bloqueo sesgado es demasiado abstracta. En la práctica, comprenda cómo se producen los bloqueos sesgados y cómo actualizar [Combate real]

¡Acostúmbrate a escribir juntos! Este es el tercer día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento

actualización de bloqueo

  • Arriba presentamos principalmente lo que son bloqueos de polarización, bloqueos livianos y bloqueos pesados. Y analice las diferencias y los escenarios de uso de los tres. Recuerde la operación de actualización de conjuntos enteros en el capítulo Redis. En candados, también diseñamos ascensos y descensos de candados. Arriba también introdujimos bloqueos sesgados cuando no hay competencia y bloqueos livianos cuando hay competencia.
  • Pero las cerraduras ligeras son operaciones cas y esperas de giro. El giro solo es adecuado para situaciones con menos simultaneidad. Si hay muchos subprocesos concurrentes, puede llevar mucho tiempo adquirir el bloqueo, y la sobrecarga durante el giro también es enorme, por lo que es necesario actualizar el bloqueo ligero. Entonces, ¿cuándo es el momento de actualizar las cerraduras pesadas? El número de giros también se establece en la JVM, y si supera un cierto número de veces, se actualizará a un bloqueo pesado.

Cerradura ligera de actualización de bloqueo de polarización

  • Personalmente, creo que el enfoque sigue estando en el proceso de escalada de bloqueos. Debido a que el bloqueo sesgado no se revocará de forma activa, el proceso de escalada de bloqueo implica escenarios como la revocación de bloqueo por lotes y el sesgo de bloqueo por lotes.

imagen-20211213152554303.png

  • Recuerde la estructura de almacenamiento del bloqueo sesgado en la marca del objeto de bloqueo, los últimos tres dígitos son 101 para indicar el bloqueo sesgado. Acerca del registro de bloqueo es el puntero del objeto de registro de bloqueo en la parte superior de la pila de subprocesos mencionada anteriormente. Acerca del registro de bloqueo, la marca de todo el objeto de bloqueo se almacena internamente, y aquí debemos prestar atención a EPOCH, que se traduce como el significado de época. Simplemente entendemos que la versión es buena.
  • Hablando de números de versión, también debemos estar familiarizados con las dos configuraciones de propiedad de JVM para bloqueos sesgados

imagen-20211213153045152.png

  • La revocación de bloqueo parcial se produce cuando se produce una escalada de bloqueo ligero. Si la JVM encuentra que el número de revocaciones de bloqueo para un determinado tipo de bloqueo es mayor o igual -XX:BiasedLockIngBulkRebiasThreshold=20, declarará inválido el bloqueo sesgado. Para invalidar el bloqueo de polarización es agregar 1 al número de versión, es decir, EPOCH+1;
  • Cuando el número total de revocaciones de un bloqueo de clase es mayor o igual a -XX:BiasedLockingBulkRevokeThreshold=40, los bloqueos subsiguientes serán de forma predeterminada bloqueos ligeros.
class Demo{
    String userName;
}
public class LockRevoke {
    public static void main(String[] args) throws InterruptedException {
        List<Demo> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(new Demo());
        }
        final Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                for (int i = 0; i < 99; i++) {
                    Demo demo = list.get(i);
                    synchronized (demo) {
                    }
                }
                TimeUnit.SECONDS.sleep(100000);
            }
        });

        final Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                synchronized (list.get(99)) {
                    System.out.println("第100个对象上锁中,并持续使用该对象" + ClassLayout.parseInstance(list.get(99)).toPrintable());
                    TimeUnit.SECONDS.sleep(99999);
                }
            }
        });

        final Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    Demo demo = list.get(i);
                    synchronized (demo) {


                        if (i == 18) {
                            System.out.println("发送第19次锁升级,list.get(18)应该是轻量级锁" + ClassLayout.parseInstance(list.get(18)).toPrintable());
                        }
                        if (i == 19) {
                            System.out.println("发送第20次锁升级,会发生批量重偏向;纪元+1;后续偏向锁都会偏向当前线程;list.get(19)应该是轻量级锁" + ClassLayout.parseInstance(list.get(19)).toPrintable());
                            System.out.println("因为第100对象仍然在使用,需要修改起纪元" + ClassLayout.parseInstance(list.get(99)).toPrintable());
                        }
                        if (i == 29) {
                            System.out.println("在批量重偏向之后;因为第一次偏向锁已经失效了,所以这里不是轻量级而是偏向该线程的偏向锁" + ClassLayout.parseInstance(list.get(29)).toPrintable());
                        }
                        if (i == 39) {
                            System.out.println("发送第40次锁升级,发生批量锁撤销;这里应该是轻量级锁后续都是轻量级" + ClassLayout.parseInstance(list.get(39)).toPrintable());
                        }
                    }
                }

            }
        });
        
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("第一次上锁后list.get(0)应该偏向锁:" + ClassLayout.parseInstance(list.get(0)).toPrintable());
        System.out.println("第一次上锁后list.get(19)应该偏向锁:" + ClassLayout.parseInstance(list.get(19)).toPrintable());
        System.out.println("第一次上锁后list.get(29)应该偏向锁:" + ClassLayout.parseInstance(list.get(29)).toPrintable());
        System.out.println("第一次上锁后list.get(39)应该偏向锁:" + ClassLayout.parseInstance(list.get(39)).toPrintable());
        System.out.println("第一次上锁后list.get(99)应该偏向锁:" + ClassLayout.parseInstance(list.get(99)).toPrintable());
        t3.start();
       

    }

}
复制代码
  • Lo anterior es un caso típico de integración de sesgo de bloqueo pesado sesgado y revocación de bloqueo sesgado.
  • En primer lugar, nuestro subproceso t1 es el primero en bloquear los primeros 99 objetos y liberarlos de inmediato, porque nuestra configuración de vm se retrasa para cancelar el bloqueo de polarización, consulte el comienzo del artículo para saber cómo configurarlos.
  • El segundo hilo t2 solo bloquea el último objeto, la diferencia es que está permanentemente ocupado y no se libera después de estar bloqueado. Entonces otros no podrán adquirir el candado del último objeto.
  • El tercer subproceso comienza a apropiarse de los recursos con el objeto inicializado anteriormente. El tercer subproceso solo realiza un bucle 40 veces, porque el número máximo predeterminado de bloqueos sesgados de revocación por parte de la JVM es 40 veces. Hay cerraduras ligeras en la parte posterior.
  • Debido a que el tercer subproceso se volverá a sesgar en lotes, no se provocará la revocación posterior del bloqueo sesgado. Si ve la revocación del bloqueo por lotes, debe abrir un subproceso para bloquear. Entonces, el subproceso 4 continuará provocando la cancelación, pero debe ejecutarse después del subproceso 4; de lo contrario, t3 y t4 se ejecutarán al mismo tiempo, lo que provocará bloqueos pesados, porque uno de los escenarios de bloqueos pesados ​​es: 1 bloqueo de sesgo, 1 candado ligero, 1 está solicitando que active el candado pesado
  • Cuando i==18, es decir, el elemento 19, está bloqueado en el tercer subproceso, porque el bloqueo de polarización se ha bloqueado antes, aunque se libera el bloqueo, el bloqueo de polarización en sí no se liberará. Entonces, en este momento, el elemento 19 tiene primero la revocación del bloqueo y luego se aplica el bloqueo ligero. Así que aquí hay un bloqueo ligero al predecir el objeto 19.
  • Luego, pase a i==19, que es el vigésimo elemento, porque la revocación total de la clase predeterminada de JVM mayor o igual a 20 provocará un nuevo sesgo del lote. ¿Qué quieres decir? Bloquear antes de i==19 en t3 es liviano. Después de i==19, el bloqueo estará sesgado cuando esté bloqueado, pero está sesgado hacia el subproceso 3, no hacia el subproceso 1. Aquí podemos comparar con el primer diseño de memoria i==19, excepto que la identificación del hilo es diferente, también hay una época diferente,

imagen-20211213161634774.png

  • ¿Por qué debería comenzar un hilo separado para bloquear list.get (99) arriba? Es para probar que cuando se produce un nuevo sesgo de lotes, puede ver intuitivamente que la información de época de bloqueo en uso se ha modificado, para que no se descarte el bloqueo.

imagen-20211213162536276.png

  • Podemos ver que cuando se produce un nuevo sesgo por lotes, la información de la época de bloqueo en uso se actualizará. Si no se actualiza, la JVM lo considerará como un bloqueo de sesgo abandonado. Por supuesto, después de que se produzca un resesgo por lotes, el bloqueo de objeto no se revocará cuando se adquiera de nuevo el bloqueo de objeto. Debido a que se abandonó el bloqueo anterior, obtengamos la información del bloqueo posterior. Echemos un vistazo a list.get(29).

imagen-20211213162904537.png

  • El cuarto subproceso continúa provocando la revocación después del tercer subproceso, y cuando el número total de revocaciones llega a 40, la JVM pensará que los bloqueos posteriores de este tipo no son adecuados para bloqueos sesgados y son directamente bloqueos ligeros.

imagen-20211213164055601.png

Supongo que te gusta

Origin juejin.im/post/7087740752398139399
Recomendado
Clasificación