Por favor, dígame la diferencia entre el bloqueo pesimista y el bloqueo optimista.

Los bloqueos pesimistas y los bloqueos optimistas no son un "bloqueo" específico sino un concepto básico de programación concurrente, basado en la perspectiva de la sincronización concurrente. El bloqueo optimista y el bloqueo pesimista aparecieron por primera vez en el diseño de bases de datos y fueron introducidos gradualmente por el paquete concurrente de Java.

bloqueo pesimista

Los bloqueos pesimistas creen que las operaciones concurrentes en los mismos datos deben modificarse y tomar la forma de bloqueo Los pesimistas creen que las operaciones concurrentes sin bloqueos inevitablemente causarán problemas. Muchos de estos mecanismos de bloqueo se utilizan en bases de datos relacionales tradicionales, como bloqueos de fila, bloqueos de tabla, bloqueos de lectura, bloqueos de escritura, etc., todos los cuales se bloquean antes de que se realicen las operaciones. Los bloqueos exclusivos como Synchronized y ReentrantLock en Java se implementan mediante bloqueo pesimista.

bloqueo optimista

El bloqueo optimista es justo lo contrario del bloqueo pesimista. Cuando adquiere datos, no se preocupa de que los datos se modifiquen, y no se bloquea cada vez que adquiere datos, pero al actualizar los datos, determina si los datos existentes son los mismos. como los datos originales. Es consistente juzgar si los datos son operados por otros subprocesos. Si no son modificados por otros subprocesos, actualizará los datos. Si son modificados por otros subprocesos, no actualizarán los datos. Lock es un caso típico de implementación de bloqueo optimista.

Caso Típico

(1) Bloqueo pesimista: palabra clave sincronizada y clases relacionadas con la interfaz de bloqueo

La implementación de bloqueos pesimistas en Java incluye la palabra clave sincronizada y las clases relacionadas con bloqueo. Tomemos la interfaz de bloqueo como ejemplo. Por ejemplo, la clase de implementación ReentrantLock of Lock, el método lock() en la clase es para ejecutar el bloqueo, y el método unlock() es para ejecutar Unlock. Antes de procesar los recursos, primero debe bloquear y obtener el bloqueo, y luego desbloquear el bloqueo después del procesamiento, que es una idea de bloqueo pesimista muy típica.

(2) Cerradura optimista: clase atómica

Un caso típico de bloqueo optimista son las clases atómicas. Por ejemplo, AtomicInteger utiliza la idea de bloqueo optimista al actualizar datos. Múltiples subprocesos pueden operar en la misma variable atómica al mismo tiempo.

(3) Gran gozo y gran tristeza: base de datos

Hay bloqueos pesimistas y bloqueos optimistas en la base de datos. Por ejemplo, si elegimos la instrucción select for update en MySQL, es un bloqueo pesimista y no se permite que un tercero modifique los datos antes de confirmar, lo que por supuesto causará una cierta pérdida de rendimiento, lo cual no es deseable en el caso de alta concurrencia. En cambio, podemos implementar el bloqueo optimista en la base de datos utilizando un campo de versión. No es necesario bloquear al adquirir y modificar datos, pero cuando estemos listos para actualizar los datos después de adquirir y modificar los datos, verificaremos si el número de versión es el mismo que el número de versión al adquirir los datos. coherente, lo actualizaremos directamente. Significa que otros subprocesos han modificado los datos durante el cálculo, por lo que puedo optar por volver a obtener los datos, volver a calcular y luego intentar actualizar los datos nuevamente.

Un ejemplo de un bloqueo pesimista para una base de datos:

seleccione * de la cuenta donde name="Erica" ​​para actualizar

Esta instrucción sql bloquea todos los registros en la tabla de cuentas que cumplen con las condiciones de recuperación ( name="Erica" ​​). Antes de que se confirme la transacción (los bloqueos en el proceso de transacción se liberarán cuando se confirme la transacción), el mundo exterior no puede modificar estos registros.

Un ejemplo de bloqueo optimista para una base de datos:

Supongamos que la versión es 1 cuando se recuperan los datos.

ACTUALIZAR estudiante

COLOCAR

nombre='Pequeño Li',

versión = 2 DONDE id = 100 Y versión = 1

Introducción a CAS

La mayor parte del bloqueo optimista en Java se implementa a través de la operación CAS (CompareAndSwap, compare and exchange). CAS es una instrucción atómica sincronizada de subprocesos múltiples. La operación CAS contiene tres información importante, a saber, la ubicación de la memoria, el valor original esperado y el valor nuevo. Si el valor de la ubicación de la memoria es igual al valor original esperado, entonces el valor de la ubicación se puede actualizar al nuevo valor; de lo contrario, no se realiza ninguna modificación.

Nota complementaria: aunque ReentrantLock también se implementa a través de CAS, es un bloqueo pesimista.

Desventaja: problema ABA.

CAS可能会造成ABA的问题,ABA问题指的是,线程拿到了最初的预期原值A,然而在将要进行CAS的时候,被其他线程抢占了执行权,把此值从A变成了B,然后其他线程又把此值从B变成A,然而此时的 A 值已经并非原来的 A 值了,但最初的线程并不知道这个情况,在它进行 CAS 的时候,就会误认为它从来没有被修改过,只对比了预期原值为 A 就进行了修改,这就造成了 ABA 的问题。

以警匪剧为例,假如某人把装了100W现金的箱子放在了家里,几分钟之后要拿它去赎人,然而在趁他不注意的时候,进来了一个小偷,用空箱子换走了装满钱的箱子,当某人进来之后看到箱子还是一模一样的,他会以为这就是原来的箱子,就拿着它去赎人了,这种情况肯定有问题,因为箱子已经是空的了,这就是 ABA 的问题。

JDK在1.5时提供了AtomicStampedReference类也可以解决ABA的问题,此类维护了一个“版本号”Stamp,每次在比较时不止比较当前值还比较版本号,这样就解决了 ABA 的问题。

综合分析实例:

如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作 员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可见,如果面对几百上千个并发,这样的情况将导致怎样的后果。乐观锁机制在一定程度上解决了这个问题。

乐观锁,大多是基于数据版本( version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。同时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号等于数据库表当前版本号,则予以更新,否则认为是过期数据。

对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

1 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 50 ( 50( 100-$50 )。

2 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 20 ( 20 ( 100-$20 )。

3 操作员 A 完成了修改工作,将 version=1 的数据连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本等于数据库记录当前版本,数据被更新,同时数据库记录 version 更新为 2(set version=version+1 where version=1) 。

4 操作员 B 完成了数据录入操作,也将 version=1 的数据试图向数据库提交( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 1 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须等于记录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

悲观锁乐观锁优缺点:

优点:

悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。

缺点:

需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。

使用场景

有一种说法认为,悲观锁由于它的操作比较重量级,不能多个线程并行执行,而且还会有上下文切换等动作,所以悲观锁的性能不如乐观锁好,应该尽量避免用悲观锁,这种说法是不正确的。因为虽然悲观锁确实会让得不到锁的线程阻塞,但是这种开销是固定的。悲观锁的原始开销确实要高于乐观锁,但是特点是一劳永逸,就算一直拿不到锁,也不会对开销造成额外的影响。反观乐观锁虽然一开始的开销比悲观锁小,但是如果一直拿不到锁,或者并发量大,竞争激烈,导致不停重试,那么消耗的资源也会越来越多,甚至开销会超过悲观锁。所以,同样是悲观锁,在不同的场景下,效果可能完全不同。

(1) 悲观锁适合用于并发写入多、临界区代码复杂、竞争激烈等场景,这种场景下悲观锁可以避免大量的无用的反复尝试等消耗。

(2) 乐观锁适用于大部分是读取,少部分是修改的场景,也适合虽然读写都很多,但是并发并不激烈的场景。在这些场景下,乐观锁不加锁的特点能让性能大幅提高。

Supongo que te gusta

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