Modo de inmutabilidad: modo de diseño de objeto inmutable

Tema: Modelo de inmutabilidad: ¿Cómo usar la inmutabilidad para resolver problemas de concurrencia?

"Varios hilos tienen problemas concurrentes para leer y escribir la misma variable compartida al mismo tiempo ". Una de las condiciones necesarias aquí es leer y escribir . Si solo hay lectura y no escritura, no hay problema de concurrencia.

Para resolver el problema de concurrencia, de hecho, la forma más simple es hacer que la variable compartida sea de solo lectura, pero no de escritura. Este enfoque es tan importante que se ha elevado a un patrón de diseño para resolver problemas de concurrencia:Modo de inmutabilidad. ** La llamada inmutabilidad, en términos simples, es que una vez que se crea un objeto, el estado no cambiará. ** En otras palabras, una vez que se asigna una variable, no se permite modificarla (sin operación de escritura); ninguna operación de modificación significa que se mantiene la inmutabilidad.

Implemente rápidamente clases inmutables

Es bastante simple implementar una clase con inmutabilidad. ** Establezca todas las propiedades de una clase en final y solo permita métodos de solo lectura, entonces esta clase es básicamente inmutable. ** Un enfoque más estricto es que la clase en sí misma es final, lo que significa que no se permite la herencia. Debido a que las subclases pueden anular los métodos de la clase padre, es posible cambiar la inmutabilidad, por lo que se recomienda utilizar este enfoque más estricto.

Muchas clases en el SDK de Java son inmutables, pero debido a que son demasiado simples de usar, eventualmente se ignoran. Por ejemplo, con frecuencia usado Stringy Long, Integer, Doubley otros tipos básicos de envasado están equipadas con inmutabilidad, la seguridad hilo de estos objetos está asegurada por la inmutabilidad. Si observa detenidamente las declaraciones, propiedades y métodos de estas clases, encontrará que cumplen estrictamente los tres requisitos de las clases inmutables: las clases y las propiedades son finales y todos los métodos son de solo lectura.

Puede que se sorprenda cuando vea esto. El método String de Java también tiene una operación de reemplazo de caracteres similar. ¿Cómo puede decir que todos los métodos son de solo lectura? Expliquemos este problema junto con el código fuente de String. El siguiente código de muestra se deriva del SDK de Java 1.8 y se ha modificado ligeramente. Solo se retienen los métodos de valor de atributo clave [] y replace (). Encontrará: clase String y El valor del atributo [] es final, y la implementación del método replace () no modifica el valor [], pero devuelve la cadena reemplazada como el valor de retorno.

public final class String {
  private final char value[];
  // 字符替换
  String replace(char oldChar, 
      char newChar) {
    //无需替换,直接返回this  
    if (oldChar == newChar){
      return this;
    }

    int len = value.length;
    int i = -1;
    /* avoid getfield opcode */
    char[] val = value; 
    //定位到需要替换的字符位置
    while (++i < len) {
      if (val[i] == oldChar) {
        break;
      }
    }
    //未找到oldChar,无需替换
    if (i >= len) {
      return this;
    } 
    //创建一个buf[],这是关键
    //用来保存替换后的字符串
    char buf[] = new char[len];
    for (int j = 0; j < i; j++) {
      buf[j] = val[j];
    }
    while (i < len) {
      char c = val[i];
      buf[i] = (c == oldChar) ? 
        newChar : c;
      i++;
    }
    //创建一个新的字符串返回
    //原字符串不会发生任何变化
    return new String(buf, true);
  }
}

Al analizar la implementación de String, es posible que haya descubierto que si tiene una clase inmutable, debe proporcionar una función de modificación similar. ¿Qué debe hacer? El método es muy simple, es crear un nuevo objeto inmutable , que es una diferencia importante de los objetos mutables, los objetos mutables a menudo modifican sus propias propiedades.

Todas las operaciones de modificación crean un nuevo objeto inmutable. Es posible que tenga esta preocupación: ¿Hay demasiados objetos creados, demasiada memoria? Sí, esto es realmente un desperdicio, ¿cómo resolverlo?

Use el modo mosca para evitar crear objetos duplicados

Si está familiarizado con los patrones de diseño orientado a objetos, creo que puede pensar enPatrón de peso mosca (patrón de peso mosca). El uso del modelo Flyweight puede reducir la cantidad de objetos creados, lo que reduce el uso de memoria. En el lenguaje Java, las clases de contenedor de estos tipos de datos básicos, como Long, Integer, Short y Byte, utilizan el patrón Flyweight.

Tomemos la clase Long como ejemplo para ver cómo usa el patrón Flyweight para optimizar la creación de objetos.

El patrón Flyweight es esencialmente un grupo de objetos , y la lógica para crear objetos usando el patrón Flyweight también es muy simple: antes de crear, primero vaya al grupo de objetos para ver si existe; si ya existe, use los objetos en el grupo de objetos; Si no existe, se creará un nuevo objeto y el objeto recién creado se colocará en el grupo de objetos.

La clase Long no copia el metamodelo compartido. Long mantiene un grupo de objetos estáticos, que solo almacena en caché los números entre [-128,127]. Este grupo de objetos se crea cuando se inicia la JVM, y este grupo de objetos ha sido Ninguno cambiará, es decir, es estático. La razón para adoptar este diseño es que hay 264 tipos de estados del objeto Long, que es demasiado para almacenar en caché, y la tasa de utilización de números entre [-128,127] es la más alta. El siguiente código de muestra es de Java 1.8. El método valueOf () utiliza el caché LongCache, que puede combinar para profundizar su comprensión.

Long valueOf(long l) {
  final int offset = 128;
  // [-128,127]直接的数字做了缓存
  if (l >= -128 && l <= 127) { 
    return LongCache
      .cache[(int)l + offset];
  }
  return new Long(l);
}
//缓存,等价于对象池
//仅缓存[-128,127]直接的数字
static class LongCache {
  static final Long cache[] 
    = new Long[-(-128) + 127 + 1];

  static {
    for(int i=0; i<cache.length; i++)
      cache[i] = new Long(i-128);
  }
}

IntegerY el Stringtipo de objeto no es adecuado bloqueo", de hecho, prácticamente todos los tipos básicos de los envases son de bloqueo no es adecuado debido a que utilizan dentro del patrón de peso mosca, que puede conducir a las miradas de bloqueo de propiedad, de hecho, son comunes. Por ejemplo En el siguiente código, la intención es que A use el bloqueo al y B use el bloqueo bl, y cada uno maneje el suyo sin verse afectado, pero en realidad al y bl son un objeto, y como resultado, A y B comparten un bloqueo.

class A {
  Long al=Long.valueOf(1);
  public void setAX(){
    synchronized (al) {
      //省略代码无数
    }
  }
}
class B {
  Long bl=Long.valueOf(1);
  public void setBY(){
    synchronized (bl) {
      //省略代码无数
    }
  }
}

Notas sobre el uso del modo de inmutabilidad

Al utilizar el modo de inmutabilidad, debe prestar atención a los siguientes dos puntos:

  1. Todas las propiedades del objeto son finales y no pueden garantizar la inmutabilidad;
  2. Los objetos inmutables también deben publicarse correctamente.

En el lenguaje Java, una vez que se asigna un atributo modificado final, no se puede modificar, pero si el tipo del atributo es un objeto ordinario, entonces se puede modificar el atributo del objeto ordinario. Por ejemplo, en el siguiente código, aunque la propiedad foo de Bar es final, la edad de propiedad de foo todavía se puede establecer a través del método setAge (). Por lo tanto, cuando se usa el modo Inmutabilidad, es necesario confirmar dónde está el límite de la inmutabilidad y si el objeto atributo también debe ser inmutable.

class Foo{
  int age=0;
  int name="abc";
}
final class Bar {
  final Foo foo;
  void setAge(int a){
    foo.age=a;
  }
}

Veamos cómo publicar objetos inmutables correctamente. Aunque los objetos inmutables son seguros para subprocesos, no significa que los objetos que hacen referencia a estos objetos inmutables sean seguros para subprocesos. Por ejemplo, en el siguiente código, Foo es inmutable y seguro para subprocesos, pero la clase Bar no es segura para subprocesos. La clase Bar contiene una referencia a Foo. La modificación de la referencia a foo no garantiza la visibilidad en múltiples subprocesos Y atomicidad.

//Foo线程安全
final class Foo{
  final int age=0;
  final int name="abc";
}
//Bar线程不安全
class Bar {
  Foo foo;
  void setFoo(Foo f){
    this.foo=f;
  }
}

Si su programa solo necesita foo para mantener la visibilidad , sin garantizar la atomicidad, puede declarar foo como una variable volátil, de modo que se pueda garantizar la visibilidad. Si su programa necesita garantizar la atomicidad , se puede lograr a través de la clase atómica. El siguiente código de muestra es una implementación de atomización de inventario razonable, que debería ser familiar, entre ellos, la atomicidad de las referencias de objetos inmutables se resuelve mediante el uso de clases atómicas.

public class SafeWM {
 
  final AtomicReference<WMRange> rf = new AtomicReference<>(new WMRange(0,0));
  
  // 设置库存上限
  void setUpper(int v){
    while(true){
      WMRange or = rf.get();
      // 检查参数合法性
      if(v < or.lower){
        throw new IllegalArgumentException();
      }
      WMRange nr = new WMRange(v, or.lower);
      if(rf.compareAndSet(or, nr)){
        return;
      }
    }
  }
  
   class WMRange{
    final int upper;
    final int lower;
    WMRange(int upper,int lower){
    //省略构造函数实现
    }
  }
}

Resumen

Usando el modo de Inmutabilidad para resolver problemas de concurrencia, tal vez te sientas un poco extraño, de hecho, estás disfrutando de sus resultados todos los días. Los tipos básicos de clases de empaquetado como String y Long, Integer y Double en el lenguaje Java son inmutables, y la seguridad de los subprocesos de estos objetos está garantizada por la inmutabilidad. El modo de inmutabilidad es la forma más fácil de resolver problemas de concurrencia. Se recomienda que cuando intente resolver un problema de concurrencia, primero pruebe el modo de inmutabilidad para ver si se puede resolver rápidamente.

Un objeto con inmutabilidad tiene solo un estado, que está determinado por todos los atributos inmutables dentro del objeto. De hecho, hay un objeto inmutable más simple, que no tiene estado. Los objetos sin estado no tienen atributos internos, solo métodos. Además de los objetos sin estado, es posible que haya oído hablar de servicios sin estado, protocolos sin estado, etc. La apatridia tiene muchos beneficios, y el punto central es el rendimiento. En el campo de subprocesos múltiples, los objetos sin estado no tienen problemas de seguridad de subprocesos ni procesamiento de sincronización, y el rendimiento natural es muy bueno; en el campo distribuido, sin estado significa que puede extenderse horizontalmente indefinidamente, por lo que el cuello de botella de rendimiento en el campo distribuido no debe estar fuera de la nada Estado en el nodo de servicio.

Manifestación

final public class Person {//final
    private final String name;//final
    private final String address;//final

    public Person(final String name, final String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
    
    private final List<String> list;
    public List<String> getList() {
        return Collections.unmodifiableList(list);//返回不可变集合
    }
}
client
public class ImmutableClient {
    public static void main(String[] args) {

        //Share data
        Person person = new Person("Alex", "GuanSu");
        IntStream.range(0, 5).forEach(i ->
                new UsePersonThread(person).start()
        );
    }
}

public class UsePersonThread extends Thread {
    private Person person;

    public UsePersonThread(Person person) {
        this.person = person;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " print " + person.toString());
        }
    }
}

不可变和正常的对比
public class ImmutablePerformance {
    public static void main(String[] args) throws InterruptedException {

        //36470
        //35857 immutable
        long startTimestamp = System.currentTimeMillis();
        SyncObj synObj = new SyncObj();
        synObj.setName("Alex");

//        ImmutableObj synObj = new ImmutableObj("Alex");


        //10000 times
        //22856 sync
        //11856 immutable

        //100000 times
        //230175 sync
        //122096 immutable
        Thread t1 = new Thread() {
            @Override
            public void run() {
                for (long l = 0L; l < 100000; l++) {
                    System.out.println(Thread.currentThread().getName() + "=" + synObj.toString());
                }
            }
        };
        t1.start();

        Thread t2 = new Thread() {
            @Override
            public void run() {
                for (long l = 0L; l < 100000; l++) {
                    System.out.println(Thread.currentThread().getName() + "=" + synObj.toString());
                }
            }
        };
        t2.start();
        t1.join();
        t2.join();


        long endTimestamp = System.currentTimeMillis();
        System.out.println("Elapsed time " + (endTimestamp - startTimestamp));
    }
}

//不可变
final class ImmutableObj {
    private final String name;

    ImmutableObj(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "[" + name + "]";
    }
}
//可变
class SyncObj {

    private String name;

    public synchronized void setName(String name) {
        this.name = name;
    }

    @Override
    public synchronized String toString() {//synchronized同步
        try {
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "[" + name + "]";
    }
}
138 artículos originales publicados · elogiados 3 · visitas 7210

Supongo que te gusta

Origin blog.csdn.net/weixin_43719015/article/details/105691169
Recomendado
Clasificación