java - solucion - tutorial para quitar proteccion contra escritura usb
Java ReentrantReadWriteLocks: ¿cómo adquirir de forma segura el bloqueo de escritura? (12)
Lo que estás buscando es una actualización de bloqueo, y no es posible (al menos no atómicamente) usando el ReentrantReadWriteLock java.concurrent estándar. Su mejor opción es desbloquear / bloquear, y luego verificar que nadie haya hecho modificaciones entre ellos.
Lo que estás intentando hacer, forzando a todos los bloqueos de lectura a salir del camino, no es una muy buena idea. Los bloqueos de lectura están ahí por una razón, que no debe escribir. :)
EDITAR:
Como señaló Ran Biron, si su problema es la inanición (los bloqueos de lectura se establecen y se liberan todo el tiempo, nunca cayendo a cero), podría intentar usar la cola justa. Pero su pregunta no sonó como si este fuera su problema?
EDICION 2:
Ahora veo su problema, en realidad adquirió varios bloqueos de lectura en la pila y desea convertirlos en un bloqueo de escritura (actualización). Esto es, de hecho, imposible con la implementación de JDK, ya que no hace un seguimiento de los propietarios del bloqueo de lectura. Podría haber otros que tengan bloqueos de lectura que no verías, y no tiene idea de cuántos de los bloqueos de lectura pertenecen a tu hilo, sin mencionar tu pila de llamadas actual (es decir, tu ciclo está matando a todos los bloqueos de lectura, no solo el suyo, entonces su bloqueo de escritura no esperará a que los lectores concurrentes terminen, y terminará con un desastre en sus manos)
De hecho, he tenido un problema similar y terminé escribiendo mi propia cuenta de quién sabe qué cerraduras de lectura y actualizando a bloqueos de escritura. Aunque esto también era un tipo de bloqueo de lectura / escritura Copy-on-Write (que permitía a un escritor a lo largo de los lectores), por lo que era un poco diferente.
Estoy utilizando en mi código en este momento un ReentrantReadWriteLock para sincronizar el acceso a través de una estructura tipo árbol. Esta estructura es grande y es leída por muchos hilos a la vez con modificaciones ocasionales en partes pequeñas, por lo que parece ajustarse bien a la expresión de lectura y escritura. Entiendo que con esta clase en particular, uno no puede elevar un bloqueo de lectura a un bloqueo de escritura, por lo que según Javadocs uno debe liberar el bloqueo de lectura antes de obtener el bloqueo de escritura. He utilizado este patrón con éxito en contextos sin reentrada antes.
Sin embargo, lo que estoy descubriendo es que no puedo adquirir confiablemente el bloqueo de escritura sin bloquear para siempre. Como el bloqueo de lectura es reentrante y en realidad lo estoy usando como tal, el código simple
lock.getReadLock().unlock();
lock.getWriteLock().lock()
puede bloquear si he adquirido el readlock reentrante. Cada llamada para desbloquear solo reduce el conteo de espera, y el bloqueo solo se libera cuando el conteo de espera llega a cero.
EDITAR para aclarar esto, ya que no creo que lo haya explicado demasiado bien al principio: soy consciente de que no existe una escalada de bloqueo incorporada en esta clase, y que simplemente tengo que liberar el bloqueo de lectura y obtener el bloqueo de escritura. Mi problema es / fue que independientemente de lo que estén haciendo otros subprocesos, al llamar a getReadLock().unlock()
puede que no libere la retención de este thread en el bloqueo si lo adquirió de manera reentrante, en cuyo caso la llamada a getWriteLock().lock()
bloqueará para siempre ya que este hilo todavía tiene una retención en el bloqueo de lectura y por lo tanto se bloquea a sí mismo.
Por ejemplo, este fragmento de código nunca llegará a la instrucción println, incluso cuando se ejecuta singlethreaded sin ningún otro subproceso que acceda al bloqueo:
final ReadWriteLock lock = new ReentrantReadWriteLock();
lock.getReadLock().lock();
// In real code we would go call other methods that end up calling back and
// thus locking again
lock.getReadLock().lock();
// Now we do some stuff and realise we need to write so try to escalate the
// lock as per the Javadocs and the above description
lock.getReadLock().unlock(); // Does not actually release the lock
lock.getWriteLock().lock(); // Blocks as some thread (this one!) holds read lock
System.out.println("Will never get here");
Entonces pregunto, ¿hay un buen idioma para manejar esta situación? Específicamente, cuando un hilo que contiene un bloqueo de lectura (posiblemente de manera reentrante) descubre que necesita escribir algo y, por lo tanto, quiere "suspender" su propio bloqueo de lectura para seleccionar el bloqueo de escritura (bloqueando según se requiera en otros hilos a suelte sus presas en el bloqueo de lectura), y luego "levante" su agarre en el bloqueo de lectura en el mismo estado luego?
Dado que esta implementación ReadWriteLock fue diseñada específicamente para ser reentrada, seguramente hay alguna manera sensata de elevar un bloqueo de lectura a un bloqueo de escritura cuando los bloqueos pueden adquirirse de manera retrógrada. Esta es la parte crítica que significa que el enfoque ingenuo no funciona.
Supongo que el ReentrantLock
está motivado por un recorrido recursivo del árbol:
public void doSomething(Node node) {
// Acquire reentrant lock
... // Do something, possibly acquire write lock
for (Node child : node.childs) {
doSomething(child);
}
// Release reentrant lock
}
¿No puedes refactorizar tu código para mover el manejo de la cerradura fuera de la recursión?
public void doSomething(Node node) {
// Acquire NON-reentrant read lock
recurseDoSomething(node);
// Release NON-reentrant read lock
}
private void recurseDoSomething(Node node) {
... // Do something, possibly acquire write lock
for (Node child : node.childs) {
recurseDoSomething(child);
}
}
Use la bandera "justa" en ReentrantReadWriteLock. "justo" significa que las solicitudes de bloqueo se entregan por orden de llegada. Puede experimentar una depredación del rendimiento, ya que cuando se emite una solicitud de "escritura", todas las solicitudes de "lectura" posteriores se bloquearán, incluso si se hubieran podido servir mientras los bloqueos de lectura preexistentes todavía están bloqueados.
Lo que estás tratando de hacer simplemente no es posible de esta manera.
No puede tener un bloqueo de lectura / escritura que puede actualizar de lectura a escritura sin problemas. Ejemplo:
void test() {
lock.readLock().lock();
...
if ( ... ) {
lock.writeLock.lock();
...
lock.writeLock.unlock();
}
lock.readLock().unlock();
}
Ahora supongamos que dos hilos entrarían en esa función. (Y usted está asumiendo la concurrencia, ¿verdad? De lo contrario, no le importarían los bloqueos en primer lugar ...)
Supongamos que ambos hilos comenzarán al mismo tiempo y se ejecutarán igualmente rápido . Eso significa que ambos obtendrían un bloqueo de lectura, lo cual es perfectamente legal. Sin embargo, ambos intentarán adquirir el bloqueo de escritura, que NINGUNO de ellos obtendrá jamás: ¡los otros hilos restantes tienen un bloqueo de lectura!
Los bloqueos que permiten la actualización de bloqueos de lectura para escribir bloqueos son propensos a bloqueos por definición. Lo siento, pero debes modificar tu enfoque.
Lo que quieres hacer debería ser posible. El problema es que Java no proporciona una implementación que pueda actualizar bloqueos de lectura para escribir bloqueos. Específicamente, javadoc ReentrantReadWriteLock dice que no permite una actualización desde el bloqueo de lectura al bloqueo de escritura.
En cualquier caso, Jakob Jenkov describe cómo implementarlo. Ver http://tutorials.jenkov.com/java-concurrency/read-write-locks.html#upgrade para más detalles.
Por qué es necesario actualizar las cerraduras de lectura a escritura
Una actualización del bloqueo de lectura a escritura es válida (a pesar de las afirmaciones de lo contrario en otras respuestas). Puede producirse un interbloqueo, por lo que parte de la implementación es un código para reconocer interbloqueos y romperlos lanzando una excepción en un subproceso para romper el interbloqueo. Eso significa que, como parte de su transacción, debe manejar la DeadlockException, por ejemplo, haciendo el trabajo nuevamente. Un patrón típico es:
boolean repeat;
do {
repeat = false;
try {
readSomeStuff();
writeSomeStuff();
maybeReadSomeMoreStuff();
} catch (DeadlockException) {
repeat = true;
}
} while (repeat);
Sin esta capacidad, la única forma de implementar una transacción serializable que lea un montón de datos consistentemente y luego escriba algo según lo que se leyó es anticipar que la escritura será necesaria antes de comenzar y, por lo tanto, obtener bloqueos ESCRIBIR en todos los datos que son Lea antes de escribir lo que debe escribirse. Este es un KLUDGE que utiliza Oracle (SELECCIONAR PARA ACTUALIZAR ...). Además, en realidad reduce la concurrencia porque nadie más puede leer o escribir ninguno de los datos mientras se ejecuta la transacción.
En particular, liberar el bloqueo de lectura antes de obtener el bloqueo de escritura producirá resultados inconsistentes. Considerar:
int x = someMethod();
y.writeLock().lock();
y.setValue(x);
y.writeLock().unlock();
Debe saber si algún Método () o cualquier método al que llama crea un bloqueo de lectura reentrante en y! Supongamos que sabes que sí. Luego, si sueltas el bloqueo de lectura primero:
int x = someMethod();
y.readLock().unlock();
// problem here!
y.writeLock().lock();
y.setValue(x);
y.writeLock().unlock();
otro hilo puede cambiar y después de que suelte su bloqueo de lectura, y antes de que obtenga el bloqueo de escritura en él. Entonces, el valor de y no será igual a x.
Código de prueba: actualización de un bloqueo de lectura a bloques de bloqueo de escritura:
import java.util.*;
import java.util.concurrent.locks.*;
public class UpgradeTest {
public static void main(String[] args)
{
System.out.println("read to write test");
ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock(); // get our own read lock
lock.writeLock().lock(); // upgrade to write lock
System.out.println("passed");
}
}
Salida usando Java 1.6:
read to write test
<blocks indefinitely>
Entonces, ¿estamos esperando que Java incremente el conteo de semáforos de lectura solo si este hilo aún no ha contribuido al readHoldCount? Lo que significa que a diferencia de mantener un ThreadLocal readholdCount de tipo int, debe mantener ThreadLocal Set de tipo Integer (manteniendo el hasCode del hilo actual). Si esto está bien, sugeriría (al menos por ahora) que no llame a varias llamadas de lectura dentro de la misma clase, sino que use una bandera para verificar, ya sea que el bloqueo de lectura ya haya sido obtenido por el objeto actual o no.
private volatile boolean alreadyLockedForReading = false;
public void lockForReading(Lock readLock){
if(!alreadyLockedForReading){
lock.getReadLock().lock();
}
}
Se encuentra en la documentación de ReentrantReadWriteLock . Dice claramente que los hilos de lectura nunca tendrán éxito cuando intente adquirir un bloqueo de escritura. Lo que intentas lograr simplemente no es compatible. Debe liberar el bloqueo de lectura antes de adquirir el bloqueo de escritura. Una rebaja aún es posible.
Reentrada
Este bloqueo permite a los lectores y escritores volver a adquirir bloqueos de lectura o escritura en el estilo de {@link ReentrantLock}. Los lectores no reentrantes no están permitidos hasta que se hayan liberado todos los bloqueos de escritura retenidos por el hilo de escritura.
Además, un escritor puede adquirir el bloqueo de lectura, pero no viceversa. Entre otras aplicaciones, la reentrada puede ser útil cuando se mantienen bloqueos de escritura durante llamadas o devoluciones de llamadas a métodos que realizan lecturas bajo bloqueos de lectura. Si un lector intenta adquirir el bloqueo de escritura, nunca tendrá éxito.
Uso de muestra de la fuente anterior:
class CachedData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
}
¿Qué tal esto algo así?
class CachedData
{
Object data;
volatile boolean cacheValid;
private class MyRWLock
{
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public synchronized void getReadLock() { rwl.readLock().lock(); }
public synchronized void upgradeToWriteLock() { rwl.readLock().unlock(); rwl.writeLock().lock(); }
public synchronized void downgradeToReadLock() { rwl.writeLock().unlock(); rwl.readLock().lock(); }
public synchronized void dropReadLock() { rwl.readLock().unlock(); }
}
private MyRWLock myRWLock = new MyRWLock();
void processCachedData()
{
myRWLock.getReadLock();
try
{
if (!cacheValid)
{
myRWLock.upgradeToWriteLock();
try
{
// Recheck state because another thread might have acquired write lock and changed state before we did.
if (!cacheValid)
{
data = ...
cacheValid = true;
}
}
finally
{
myRWLock.downgradeToReadLock();
}
}
use(data);
}
finally
{
myRWLock.dropReadLock();
}
}
}
Java 8 ahora tiene java.util.concurrent.locks.StampedLock
con una tryConvertToWriteLock(long)
Más información en http://www.javaspecialists.eu/archive/Issue215.html
OP: simplemente desbloquee tantas veces como ingrese al candado, así de simple:
boolean needWrite = false;
readLock.lock()
try{
needWrite = checkState();
}finally{
readLock().unlock()
}
//the state is free to change right here, but not likely
//see who has handled it under the write lock, if need be
if (needWrite){
writeLock().lock();
try{
if (checkState()){//check again under the exclusive write lock
//modify state
}
}finally{
writeLock.unlock()
}
}
en el bloqueo de escritura, ya que cualquier programa concurrente de autorrespeto verifica el estado necesario.
HoldCount no se debe usar más allá de la detección de errores / monitores / fallas rápidas.
He progresado un poco en esto. Al declarar explícitamente la variable de bloqueo como ReentrantReadWriteLock
lugar de simplemente ReadWriteLock
(menos que ideal, pero probablemente un mal necesario en este caso) puedo llamar al método getReadHoldCount()
. Esto me permite obtener el número de retenciones para el subproceso actual y, por lo tanto, puedo liberar el readlock muchas veces (y volver a adquirir el mismo número después). Entonces esto funciona, como lo muestra una prueba rápida y sucia:
final int holdCount = lock.getReadHoldCount();
for (int i = 0; i < holdCount; i++) {
lock.readLock().unlock();
}
lock.writeLock().lock();
try {
// Perform modifications
} finally {
// Downgrade by reacquiring read lock before releasing write lock
for (int i = 0; i < holdCount; i++) {
lock.readLock().lock();
}
lock.writeLock().unlock();
}
Aún así, ¿va a ser esto lo mejor que puedo hacer? No se siente muy elegante, y sigo esperando que haya una manera de manejar esto de una manera menos "manual".
Esta es una vieja pregunta, pero aquí hay una solución al problema y algo de información de fondo.
Como han señalado otros, un bloqueo clásico de lector-escritor (como el JDK ReentrantReadWriteLock ) no admite inherentemente la actualización de un bloqueo de lectura a un bloqueo de escritura, porque al hacerlo es susceptible de interbloqueo.
Si necesita adquirir de forma segura un bloqueo de escritura sin antes liberar un bloqueo de lectura, existe una mejor alternativa: en su lugar, observe un bloqueo de lectura / escritura- actualización .
Escribí ReentrantReadWrite_Update_Lock y lo lancé como código abierto bajo una licencia Apache 2.0 aquí . También publiqué detalles del enfoque de la lista de correo de interés concurrente de JSR166, y el enfoque sobrevivió a un examen exhaustivo por parte de los miembros de esa lista.
El enfoque es bastante simple, y como mencioné en concurrency-interest, la idea no es completamente nueva, ya que se discutió en la lista de correo del kernel de Linux al menos hasta el año 2000. También ReaderWriterLockSlim de la plataforma .Net admite la actualización de bloqueo además. Así que efectivamente este concepto simplemente no se había implementado en Java (AFAICT) hasta ahora.
La idea es proporcionar un bloqueo de actualización además del bloqueo de lectura y el bloqueo de escritura . Un bloqueo de actualización es un tipo intermedio de bloqueo entre un bloqueo de lectura y un bloqueo de escritura. Al igual que el bloqueo de escritura, solo un hilo puede adquirir un bloqueo de actualización a la vez. Pero al igual que un bloqueo de lectura, permite el acceso de lectura al hilo que lo contiene, y al mismo tiempo a otros hilos que tienen bloqueos de lectura regulares. La característica clave es que el bloqueo de actualización se puede actualizar desde su estado de solo lectura, a un bloqueo de escritura, y esto no es susceptible de interbloqueo porque solo un hilo puede contener un bloqueo de actualización y estar en posición de actualizar a la vez.
Esto es compatible con la actualización de bloqueo y, además, es más eficiente que un bloqueo convencional de lector y escritor en aplicaciones con patrones de acceso de lectura antes de escritura , ya que bloquea los hilos de lectura durante periodos de tiempo más cortos.
El uso de ejemplo se proporciona en el sitio . La biblioteca tiene una cobertura de prueba del 100% y se encuentra en el centro de Maven.