JavaRush /Blog Java /Random-ES /Nivel 26. Respuestas a las preguntas de la entrevista sob...
zor07
Nivel 31
Санкт-Петербург

Nivel 26. Respuestas a las preguntas de la entrevista sobre el tema del nivel. Parte 2. Preguntas 6-9, 11-12

Publicado en el grupo Random-ES
Nivel 26. Respuestas a las preguntas de la entrevista sobre el tema del nivel.  Parte 2. Preguntas 6-9, 11-12 - 1

6. ¿Qué es Cancarenzi?

La concurrencia es una biblioteca de clases en Java que contiene clases especiales optimizadas para trabajar en múltiples subprocesos. Estas clases se recogen en un paquete java.util.concurrent. Se pueden dividir esquemáticamente según su funcionalidad de la siguiente manera: Nivel 26. Respuestas a las preguntas de la entrevista sobre el tema del nivel.  Parte 2. Preguntas 6-9, 11-12 - 2Colecciones concurrentes : un conjunto de colecciones que funcionan de manera más eficiente en un entorno de subprocesos múltiples que las colecciones universales estándar del java.utilpaquete. En lugar de un contenedor básico Collections.synchronizedListque bloquea el acceso a toda la colección, se utilizan bloqueos en segmentos de datos o se optimiza el trabajo para la lectura paralela de datos mediante algoritmos sin espera. Colas : colas con y sin bloqueo con soporte para subprocesos múltiples. Las colas sin bloqueo están diseñadas para brindar velocidad y operación sin bloquear subprocesos. Las colas de bloqueo se utilizan cuando es necesario "ralentizar" los subprocesos "Productor" o "Consumidor" si no se cumplen algunas condiciones, por ejemplo, la cola está vacía o desbordada, o no hay un "Consumidor" libre. Los sincronizadores son utilidades auxiliares para sincronizar subprocesos. Son un arma poderosa en la computación "paralela". Ejecutores : contiene excelentes marcos para crear grupos de subprocesos, programar tareas asincrónicas y obtener resultados. Locks : representa mecanismos de sincronización de subprocesos alternativos y más flexibles en comparación con los synchronizedbásicos . Atómica : clases con soporte para operaciones atómicas en primitivas y referencias. Fuente:waitnotifynotifyAll

7. ¿Qué clases de Kankarensi conoces?

La respuesta a esta pregunta está perfectamente expuesta en este artículo . No veo el sentido de reimprimirlo todo aquí, así que daré descripciones sólo de aquellas clases con las que tuve el honor de familiarizarme brevemente. ConcurrentHashMap<K, V> : a diferencia Hashtablede los bloques y synhronizeden HashMap, los datos se presentan en forma de segmentos, divididos en hashes de claves. Como resultado, se accede a los datos por segmentos en lugar de por un solo objeto. Además, los iteradores representan datos durante un período de tiempo específico y no los arrojan ConcurrentModificationException. AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray : ¿qué pasa si en una clase necesitas sincronizar el acceso a una variable simple de tipo int? Puede utilizar construcciones con synchronizedy cuando utilice operaciones atómicas set/get. volatilePero puedes hacerlo aún mejor usando nuevas clases Atomic*. Debido al uso de CAS, las operaciones con estas clases son más rápidas que si se sincronizan mediante synchronized/volatile. Además, existen métodos para la suma atómica en una cantidad determinada, así como para incrementar/disminuir.

8. ¿Cómo funciona la clase ConcurrentHashMap?

En el momento de su introducción, ConcurrentHashMaplos desarrolladores de Java necesitaban la siguiente implementación de mapa hash:
  • Seguridad del hilo
  • Sin bloqueos en toda la mesa al acceder a ella
  • Es deseable que no haya bloqueos de tabla al realizar una operación de lectura.
Las principales ideas de implementación ConcurrentHashMapson las siguientes:
  1. Elementos del mapa

    A diferencia de los elementos HashMap, Entryen ConcurrentHashMapse declaran como volatile. Esta es una característica importante, también debido a cambios en JMM .

    static final class HashEntry<K, V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K, V> next;
    
        HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
            this .key = key;
            this .hash = hash;
            this .next = next;
            this .value = value;
         }
    
        @SuppressWarnings("unchecked")
        static final <K, V> HashEntry<K, V>[] newArray(int i) {
            return new HashEntry[i];
        }
    }
  2. Función hash

    ConcurrentHashMapTambién se utiliza una función hash mejorada.

    Déjame recordarte cómo era en HashMapJDK 1.2:

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    Versión de ConcurrentHashMap JDK 1.5:

    private static int hash(int h) {
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        return h ^ (h >>> 16);
    }

    ¿Por qué es necesario hacer que la función hash sea más compleja? Las tablas en un mapa hash tienen una longitud determinada por una potencia de dos. Para códigos hash cuyas representaciones binarias no difieren en posición baja y alta, tendremos colisiones. Aumentar la complejidad de la función hash simplemente resuelve este problema, reduciendo la probabilidad de colisiones en el mapa.

  3. Segmentos

    El mapa se divide en N segmentos diferentes (16 por defecto, el valor máximo puede ser 16 bits y es una potencia de dos). Cada segmento es una tabla de elementos de mapa segura para subprocesos. Aumentar el número de segmentos fomentará que las operaciones de modificación abarquen varios segmentos, lo que reducirá la probabilidad de bloqueo en tiempo de ejecución.

  4. Nivel de simultaneidad

    Este parámetro afecta el uso de la tarjeta de memoria y el número de segmentos de la tarjeta.

    El número de segmentos se elegirá como la potencia de dos más cercana mayor que concurrencyLevel. Reducir el nivel de concurrencia hace que sea más probable que los subprocesos bloqueen segmentos del mapa al escribir. La sobreestimación del indicador conduce a un uso ineficiente de la memoria. Si solo un hilo modificará el mapa y el resto leerá, se recomienda utilizar el valor 1.

  5. Total

    Entonces, las principales ventajas y características de implementación ConcurrentHashMap:

    • hashmapEl mapa tiene una interfaz de interacción similar a
    • Las operaciones de lectura no requieren bloqueos y se realizan en paralelo
    • Las operaciones de escritura a menudo también se pueden realizar en paralelo sin bloquear
    • Al crear se indica el requerido concurrencyLevel, determinado por lectura y escritura de estadísticas.
    • Los elementos del mapa tienen un valor valuedeclarado comovolatile
    Fuente: Cómo funciona ConcurrentHashMap

9. ¿Qué es la clase Lock?

Para controlar el acceso a un recurso compartido, podemos utilizar bloqueos como alternativa al operador sincronizado. La funcionalidad de bloqueo está empaquetada en java.util.concurrent.locks. Primero, el hilo intenta acceder al recurso compartido. Si está libre, se coloca un candado en el hilo. Una vez que se completa el trabajo, se libera el bloqueo del recurso compartido. Si el recurso no está libre y ya se le ha colocado un bloqueo, entonces el hilo espera hasta que se libere este bloqueo. Las clases de bloqueo implementan una interfaz Lockque define los siguientes métodos:
  • void lock():espera hasta que se adquiera el bloqueo
  • boolean tryLock():intenta adquirir un bloqueo; si se obtiene el bloqueo, devuelve verdadero . Si no se adquiere el bloqueo, devuelve false . A diferencia del método, lock()no espera a adquirir un candado si no hay uno disponible.
  • void unlock():quita la cerradura
  • Condition newCondition():devuelve el objeto Conditionque está asociado con el bloqueo actual
La organización del bloqueo en el caso general es bastante simple: para obtener el bloqueo, se llama al método lock()y, después de terminar de trabajar con recursos compartidos, se llama al método unlock()que libera el bloqueo. El objeto Conditionle permite gestionar el bloqueo. Como regla general, para trabajar con cerraduras se utiliza una clase ReentrantLockdel paquete java.util.concurrent.locks.que implementa la interfaz Lock. Veamos el uso de la API de bloqueo de Java usando un programa pequeño como ejemplo: entonces, digamos que tenemos una clase Resourcecon un par de métodos seguros para subprocesos y métodos donde no se requiere seguridad para subprocesos.
public class Resource {

    public void doSomething(){
        // пусть здесь происходит работа с базой данных
    }

    public void doLogging(){
        // потокобезопасность для логгирования нам не требуется
    }
}
Ahora tomemos una clase que implementa la interfaz Runnabley usa métodos de clase Resource.
public class SynchronizedLockExample implements Runnable{

    // экземпляр класса Resource для работы с методами
    private Resource resource;

    public SynchronizedLockExample(Resource r){
        this.resource = r;
    }

    @Override
    public void run() {
        synchronized (resource) {
            resource.doSomething();
        }
        resource.doLogging();
    }
}
Ahora reescribamos el programa anterior usando Lock API en lugar de synchronized.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{

    private Resource resource;
    private Lock lock;

    public ConcurrencyLockExample(Resource r){
        this.resource = r;
        this.lock = new ReentrantLock();
    }

    @Override
    public void run() {
        try {
            // лочим на 10 секунд
            if(lock.tryLock(10, TimeUnit.SECONDS)){
            resource.doSomething();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //убираем лок
            lock.unlock();
        }
        // Для логгирования не требуется потокобезопасность
        resource.doLogging();
    }

}
Como puede ver en el programa, utilizamos el método tryLock()para asegurarnos de que el hilo solo espere un cierto período de tiempo. Si no obtiene un bloqueo sobre el objeto, simplemente inicia sesión y sale. Otro punto importante. Debe utilizar un bloque try-finallypara garantizar que el bloqueo se libere incluso si el método doSomething()genera una excepción. Fuentes:

11. ¿Qué es un mutex?

Un mutex es un objeto especial para sincronizar subprocesos/procesos. Pueden ser necesarios dos estados: ocupado y libre. Para simplificar, un mutex es una variable booleana que toma dos valores: ocupado (verdadero) y libre (falso). Cuando un hilo quiere la propiedad exclusiva de un objeto, marca su exclusión mutua como ocupada, y cuando termina de trabajar con él, marca su exclusión mutua como libre. Se adjunta un mutex a cada objeto en Java. Sólo la máquina Java tiene acceso directo al mutex. Está oculto al programador.

12. ¿Qué es un monitor?

Un monitor es un mecanismo especial (un fragmento de código), un complemento sobre el mutex, que garantiza su correcto funcionamiento. Después de todo, no basta con marcar que el objeto está ocupado; también debemos asegurarnos de que otros subprocesos no intenten utilizar el objeto ocupado. En Java, el monitor se implementa mediante la palabra clave synchronized. Cuando escribimos un bloque sincronizado, el compilador de Java lo reemplaza con tres fragmentos de código:
  1. Al comienzo del bloque synchronized, se agrega un código que marca el mutex como ocupado.
  2. Al final del bloque, synchronizedse agrega un código que marca el mutex como libre.
  3. Antes del bloqueo, synchronizedse agrega un código que verifica si el mutex está ocupado, luego el hilo debe esperar a que se libere.
Parte 1
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION