JavaRush /Blog Java /Random-ES /Concurrencia en Java. Tutorial: construcciones seguras pa...
0xFF
Nivel 9
Донецк

Concurrencia en Java. Tutorial: construcciones seguras para subprocesos.

Publicado en el grupo Random-ES
Después de cubrir los principales riesgos de ejecutar programas paralelos (como la atomicidad o la visibilidad ), veremos algunos diseños de clases que nos ayudarán a prevenir los errores mencionados anteriormente. Algunas de estas construcciones crean objetos seguros para subprocesos, lo que nos permite compartirlos de forma segura entre subprocesos. Como ejemplo, veremos objetos inmutables y sin estado. Otras vistas evitarán que diferentes subprocesos modifiquen datos, como las variables locales del subproceso. Puedes ver todos los códigos fuente en Github . 1. Objetos inmutables Los objetos inmutables tienen estado (tienen datos que representan el estado del objeto), pero se establecen en el momento de la creación en el constructor, una vez que se ha creado una instancia del objeto y el estado no se puede cambiar. Aunque los hilos pueden alternarse, un objeto todavía tiene un estado posible. Dado que todos los campos son de solo lectura, ningún hilo puede cambiar los datos del objeto. Debido a esto, una secuencia inmutable es inherentemente segura para subprocesos. La clase Producto demuestra una clase inmutable. Completa todos sus campos en el constructor y ninguno de ellos cambia: public final class Product { private final String id; private final String name; private final double price; public Product(String id, String name, double price) { this.id = id; this.name = name; this.price = price; } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } public boolean equals(Object x) { if (this == x) return true; if (x == null) return false; if (this.getClass() != x.getClass()) return false; Product that = (Product) x; if (!this.id.equals(that.id)) return false; if (!this.name.equals(that.name)) return false; if (this.price != that.price) return false; return true; } public int hashCode() { int hash = 17; hash = 31 * hash + this.getId().hashCode(); hash = 31 * hash + this.getName().hashCode(); hash = 31 * hash + ((Double) this.getPrice()).hashCode(); return hash; } } en algunos casos, hacer que los campos sean definitivos no será suficiente. Por ejemplo, la clase MutableProduct no es inmutable, aunque todos los campos son finales: ¿ Por qué la clase anterior no es inmutable? La razón es que permitimos recuperar una referencia de una clase. El campo ' categorías ' es una referencia mutable, por lo que una vez recibido, el cliente puede modificarlo. Para ilustrar esto, considere el siguiente programa: Y la salida de la consola: Dado que el campo 'categorías' es mutable y se obtuvo de un objeto, el cliente ha modificado esta lista. Se cambió un objeto que debería ser inmutable, lo que resultó en un nuevo estado. Si desea representar el contenido de una lista, puede utilizar una representación de lista inmutable: 2. Objetos sin estado Los objetos sin estado son similares a los objetos inmutables, pero en este caso no tienen estado, ni siquiera uno. Cuando un objeto es un objeto sin estado, no es necesario que persista ningún dato entre llamadas. Como no existe ningún estado, ningún hilo puede afectar el resultado de otro hilo llamando a los métodos del objeto. Por esta razón, los objetos sin estado son inherentemente seguros para subprocesos. La clase ProductHandler es un ejemplo de este tipo de objeto. Contiene múltiples operaciones en objetos Producto y no almacena ningún dato entre llamadas. El resultado de la operación es independiente de las llamadas anteriores o de cualquier dato almacenado: en el método sumCart, ProductHandler convierte la lista de productos en una matriz para usarla en un bucle for-each para recorrer todos los elementos. La lista de iteradores no es segura para subprocesos y puede generar una excepción ConcurrentModificationException si hay cambios durante la iteración. Dependiendo de tus necesidades, podrás elegir una estrategia diferente . 3. Variables locales del subproceso Las variables locales del subproceso son aquellas variables que se definen dentro de un subproceso. Ningún otro hilo los ve y no los cambiará. El primer tipo son las variables locales. En el siguiente ejemplo, la variable total se almacena en la pila del hilo: public final class MutableProduct { private final String id; private final String name; private final double price; private final List categories = new ArrayList<>(); public MutableProduct(String id, String name, double price) { this.id = id; this.name = name; this.price = price; this.categories.add("A"); this.categories.add("B"); this.categories.add("C"); } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public List getCategories() { return this.categories; } public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } } public static void main(String[] args) { MutableProduct p = new MutableProduct("1", "a product", 43.00); System.out.println("Product categories"); for (String c : p.getCategories()) System.out.println(c); p.getCategories().remove(0); System.out.println("\nModified Product categories"); for (String c : p.getCategories()) System.out.println(c); } Product categories A B C Modified Product categories B C public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public class ProductHandler { private static final int DISCOUNT = 90; public Product applyDiscount(Product p) { double finalPrice = p.getPrice() * DISCOUNT / 100; return new Product(p.getId(), p.getName(), finalPrice); } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } Solo tenga en cuenta que si define una referencia en lugar de una variable primitiva y la devuelve, abandonará sus límites. Es posible que no sepa dónde se creó el enlace devuelto. El código que llama al método sumCart podría almacenarlo en un campo estático y permitir que diferentes subprocesos accedan a él. El segundo tipo es la clase ThreadLocal . Esta clase proporciona almacenamiento independiente para cada hilo. Los valores almacenados en ThreadLocal están disponibles para cualquier código en el mismo hilo. La clase ClientRequestId muestra un ejemplo del uso de la clase ThreadLocale: La clase ProductHandlerThreadLocal usa ClientRequestId para devolver el mismo ID generado en el mismo hilo: Al ejecutar el método principal, la salida de la consola mostrará diferentes ID para cada hilo. Por ejemplo: si va a utilizar ThreadLocale, debe tener en cuenta algunos riesgos de uso al fusionar subprocesos (como en las aplicaciones de servidor). Es posible que experimente pérdidas de memoria o de información entre solicitudes. No entraré en muchos detalles sobre esto porque... El artículo “ Cómo dispararse en el pie con ThreadLocale ” demuestra bien cómo puede suceder esto. 4. Uso de la sincronización Otra forma de proporcionar acceso seguro a los subprocesos a los objetos es mediante la sincronización. Si sincronizamos todos los accesos a una referencia, entonces solo un objeto hilo accederá a ella en un momento dado. Hablaremos de esto en futuras publicaciones. 5. Conclusión Analizamos varios métodos que le permiten crear objetos simples a los que pueden acceder varios subprocesos. Es mucho más difícil evitar errores de subprocesos múltiples si un objeto puede tener múltiples estados. Por otro lado, si un objeto puede tener sólo un estado o ninguno, no tenemos que preocuparnos de que varios subprocesos accedan al objeto al mismo tiempo. El original está aquí . public class ClientRequestId { private static final ThreadLocal id = new ThreadLocal () { @Override protected String initialValue() { return UUID.randomUUID().toString(); } }; public static String get() { return id.get(); } } public class ProductHandlerThreadLocal { //Same methods as in ProductHandler class public String generateOrderId() { return ClientRequestId.get(); } } T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258 T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T3 - 126b8359-3bcc-46b9-859a-d305aff22c7e ...
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION