Portal de notas concurrentes:
1.0 Programación concurrente-Mapa mental
2.0 Programación concurrente-Fundamentos de seguridad de subprocesos
3.0 Programación concurrente-Módulo de construcción básico
4.0 Programación concurrente-Ejecución de tareas-Futuro
5.0 Programación concurrente-Rendimiento y escalabilidad de subprocesos múltiples
6.0 Programación concurrente- Bloqueo explícito y
programación concurrente sincronizada 7.0-AbstractQueuedSynchronizer
8.0 programación concurrente-variables atómicas y mecanismo de sincronización sin bloqueo
Bloqueo explícito
Antes de Java 5, el único mecanismo que se puede utilizar al coordinar el acceso a los objetos compartidos es la
synchronized
sumavolatile
. Se ha agregado Java 5ReentrantLock
.ReentrantLock
No es una forma de reemplazar el bloqueo integrado, sino una función avanzada opcional cuando el mecanismo de bloqueo integrado no es aplicable.
Bloquear 与 ReentrantLock
Lock proporciona una operación de adquisición incondicional, sondeada, temporizada e interrumpible Todos los métodos de bloqueo y desbloqueo son explícitos.
En la implementación de Lock, se debe proporcionar la misma semántica de visibilidad de memoria que los bloqueos internos, pero pueden ser diferentes en términos de semántica de bloqueo, algoritmos de programación, garantías de orden y características de rendimiento.
package java.util.concurrent.locks;
/**
* @see ReentrantLock
* @see Condition
* @see ReadWriteLock
*
* @since 1.5
* @author Doug Lea
*/
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock
Implementa Lock
la interfaz y proporciona la synchronized
memoria visible y la misma fila del mutex. Y synchronized
como lo mismo, ReentrantLock
también proporciona 可重入(可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。)
semántica de bloqueo.
¿Por qué crear un nuevo mecanismo de bloqueo que sea tan similar a los bloqueos de memoria?
En la mayoría de los casos, la cerradura integrada funciona bien, pero existen algunas limitaciones en la funcionalidad. Por ejemplo, no puede interrumpir un hilo que está esperando para adquirir un bloqueo, o no puede esperar indefinidamente mientras solicita un bloqueo.
El candado incorporado debe liberarse en el bloque de código que adquirió el candado, lo que simplifica el trabajo de codificación y logra una buena interacción con las operaciones de manejo de excepciones, pero no puede implementar las reglas de bloqueo de la estructura sin bloqueo. Estas son synchronized
las razones para su uso , pero en algunos casos, un mecanismo de bloqueo más flexible generalmente puede proporcionar una mejor vivacidad o rendimiento.
Para llamar a Lock explícitamente, el bloqueo debe liberarse finalmente. Aunque no es difícil liberar el bloqueo finalmente, puede olvidarse.
Bloqueo de sondeo y bloqueo de tiempo
El modo de adquisición de bloqueo temporizado y sondeado se
tryLock
implementa mediante métodos. En comparación con el modo de adquisición de bloqueo incondicional, tiene un mecanismo de recuperación de errores más completo.
Operación de adquisición de bloqueo interrumpible
lockInterruptibly
El método puede mantener la respuesta a la interrupción mientras obtiene el bloqueo.
¡El código está debajo de Log! ! ! ! !
Se imprime el método lock (): Después de que el hilo 1 no pueda obtener el bloqueo, esperará a que se libere el bloqueo y no responderá a la interrupción.Cuando el hilo 0 libera el bloqueo, el hilo 1 reanuda la respuesta a la interrupción.
Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock
Thread-1:already get lock
Thread-1:Interrupt
Thread-1: release unlock
El método lockInterruptiblemente () imprime: El hilo 1 puede responder a la interrupción en el tiempo después de que no puede obtener el bloqueo.
Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-1:Interrupt
Thread-1: unlock failed
Thread-1: failed desc:null
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock
Ejecute el código de muestra lockInterruptibly
y lock
la diferencia será clara .
public static void main(String[] args) throws InterruptedException {
LockTest lockTest = new LockTest();
Thread t0 = new Thread(new Runnable(){
@Override
public void run() {
lockTest.doWork();
}
});
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
lockTest.doWork();
}
});
// 启动线程t1
t0.start();
Thread.sleep(10);
// 启动线程t2
t1.start();
Thread.sleep(100);
// 线程t1没有得到锁,中断t1的等待
t1.interrupt();
}
class LockTest {
private Lock lock = new ReentrantLock();
public void doWork() {
String name = Thread.currentThread().getName();
try {
System.out.println(name + ":start get lock");
//lock.lock();
lock.lockInterruptibly();
System.out.println(name + ":already get lock");
for (int i = 0; i < 6; i++) {
Thread.sleep(1000);
System.out.println(name + ":working num "+ i);
}
} catch (InterruptedException e) {
System.out.println(name + ":Interrupt");
}finally{
try {
lock.unlock();
System.out.println(name + ": release unlock");
} catch (Exception e) {
System.out.println(name + ": unlock failed");
System.out.println(name + ": failed desc:" + e.getMessage());
}
}
}
}
Consideraciones de rendimiento
Cuando se agrega Java 5 ReentrantLock
, puede proporcionar un mejor rendimiento competitivo que los bloqueos integrados. Java 6 usa un algoritmo mejorado para administrar los bloqueos integrados, de modo que los bloqueos integrados ReentrantLock
son casi iguales en rendimiento y su escalabilidad es básicamente la misma.
Justicia
Hay
ReentrantLock
dos opciones de equidad en el constructor: crear un bloqueo injusto (predeterminado) o un bloqueo equitativo.
- Bloqueo justo: los subprocesos adquieren bloqueos en el orden en que realizan las solicitudes.
- Bloqueo injusto: si el estado del bloqueo está disponible mientras el subproceso realiza una solicitud, el subproceso omitirá todos los subprocesos en espera y adquirirá el bloqueo.
En un bloqueo justo, si otro subproceso está reteniendo el bloqueo o si otro subproceso está esperando el bloqueo en la cola, el subproceso recién solicitado se colocará en la cola. En un bloqueo injusto, solo cuando el bloqueo está retenido por un hilo, el hilo recién solicitado se colocará en la cola.
¿Por qué no queremos que todos los candados sean justos?
Al realizar operaciones de bloqueo, la sobrecarga de utilizar bloqueos justos para suspender y reanudar subprocesos puede reducir considerablemente el rendimiento. Además, en situaciones reales, las garantías de equidad estadística (para garantizar que el hilo bloqueado pueda finalmente obtener el bloqueo) suelen ser suficientes, y la sobrecarga será mucho menor. Algunos se basan en algoritmos de colas justos para garantizar la corrección del negocio, pero estos algoritmos no son comunes. En la mayoría de los casos, el rendimiento de las cerraduras injustas es superior al de las cerraduras normales.
En synchronized
y ReentrantLock
elige entre
ReentrantLock
La semántica proporcionada en las cerraduras y la memoria es la misma que la de las cerraduras integradas. Además, también proporciona algunas otras funciones: bloqueo de tiempo en espera, bloqueo interrumpible en espera, equidad y bloqueo no estructurado en bloque.
En comparación con las cerraduras explícitas, las cerraduras integradas todavía tienen grandes ventajas. El candado integrado es más familiar para los desarrolladores y es simple y compacto. ReentrantLock
El peligro es mayor que el mecanismo de sincronización, si te olvidas finally
de llamarlo en el bloque, se unlock()
ha colocado una bomba de tiempo.
En algunos casos en los que el candado integrado no puede satisfacer las necesidades, se ReentrantLock
puede utilizar como una herramienta avanzada. Se debe utilizar cuando se necesitan algunas funciones avanzadas, entre las que se ReentrantLock
incluyen: operaciones de adquisición temporizadas, sondeables e interrumpibles, colas justas y bloqueos de estructura sin bloques. De lo contrario, utilícelo primero synchronized
.
Bloqueo de lectura y escritura
ReentrantLock
Se implementa un bloqueo de mutex estándar: como máximo se mantiene un hilo cada vezReentrantLock
. Pero para mantener la integridad de los datos, la exclusión mutua suele ser una regla de bloqueo demasiado fuerte, que limita la concurrencia. La exclusión mutua es una estrategia de bloqueo conservadora. Aunque los conflictos pueden evitarse写/写
, los写/读
conflictos también pueden evitarse读/读
. Por lo tanto, si读/读
se relajan los requisitos de bloqueo de la situación, se mejorará el rendimiento del programa. En este caso, hay读-写锁
: se puede acceder a un recurso mediante varias operaciones de lectura o mediante una operación de escritura, pero no se pueden realizar las dos al mismo tiempo.
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
En 读-写锁
la estrategia de bloqueo, se permiten múltiples operaciones de lectura al mismo tiempo, pero solo se permite una operación de escritura a la vez.
ReentrantReadWriteLock
Proporciona una semántica de bloqueo importante para ambos tipos de bloqueos. Y ReentrantLock
similar,
ReentrantReadWriteLock
cuando la construcción también puede optar por ser un candado justo sin bloqueo (predeterminado) o un bloqueo justo.
- En un bloqueo justo, el hilo con el tiempo de espera más largo obtendrá el bloqueo primero. Si este bloqueo está retenido por un hilo de lectura y otro hilo solicita un bloqueo de escritura, ningún otro hilo de lector puede adquirir el bloqueo de lectura hasta que el hilo de escritura se agote y libere el bloqueo de escritura.
- En un bloqueo injusto, el orden en el que los subprocesos obtienen permisos de acceso es incierto. Es posible degradar un hilo de escritor a un hilo de lector, pero no es posible actualizar de un hilo de lector a un hilo de escritor (esto provocará un punto muerto).
Ejemplo de código de bloqueo de lectura-escritura:
public class ReadWriteMap<K,V>{
private final Map<K,V> map;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock read = lock.readLock();
private final Lock write = lock.writeLock();
public ReadWriteMap(Map<K,V> map){
this.map = map;
}
public V put(K key,V value){
write.lock();
try {
return map.put(key, value);
}finally{
write.unlock();
}
}
public V get(Object key){
read.lock();
try {
return map.get(key);
}finally{
read.unlock();
}
}
}
En comparación con la cerradura incorporada, la cerradura explícita proporciona algunas funciones ampliadas y tiene una mayor flexibilidad. Flexibilidad y mejor control de las filas de espera. Pero no ReentrantLock
se puede reemplazar por completo synchronized
. synchronized
Debe usarse sólo cuando no se pueda satisfacer la demanda.
读-写锁
Permite que varios hilos de lectura accedan simultáneamente al objeto protegido. Al acceder a estructuras de datos dominadas por operaciones de lectura, puede mejorar la escalabilidad del programa.