《Java 并发编程》共享模型之不可变

《Java 并发编程》专栏索引
《Java 并发编程》进程与线程
《Java 并发编程》共享模型之管程
《Java 并发编程》共享模型之内存
《Java 并发编程》共享模型之无锁
《Java 并发编程》共享模型之不可变
《Java 并发编程》线程池

《Java 并发编程》共享模型之不可变

  • 1. 日期转换的问题
  • 2. 不可变设计
  • 3. 无状态

1. 日期转换的问题

在运行下面的代码时,由于 SimpleDateFormat 不是线程安全的

public class Test {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    System.out.println(sdf.parse("2022-04-26"));
                } catch (Exception e) {
                    System.out.println(e);
                }
            }).start();
        }
    }
}

因此,可能会出现 java.lang.NumberFormatException 或不正确的日期解析结果,如下所示:

java.lang.NumberFormatException: For input string: ".E0"
java.lang.NumberFormatException: For input string: "44E.144E1"
java.lang.NumberFormatException: For input string: "44E.144"
java.lang.NumberFormatException: For input string: ".20222022E"
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: empty String
Mon Oct 26 00:00:00 CST 44
Tue Apr 26 00:00:00 CST 2022
Tue Apr 26 00:00:00 CST 2022

针对这个问题,可以使用同步锁 synchronized,但是会带来性能上的损失

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 20; i++) {
    new Thread(()->{
        synchronized (sdf) {
            try {
                System.out.println(sdf.parse("2022-04-26"));
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }).start();
}

还有一种思路就是不可变

如果一个对象在不能够修改其内部状态(属性)的情况下,那么它就是线程安全的(不存在并发修改)。例如,在 Java 8 以后,提供了一个新的日期格式化类:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
      new Thread(()->{
          LocalDate date = dtf.parse("2022-04-26", LocalDate::from);
          System.out.println(date);
      }).start();
}

查看 DateTimeFormatter 的源码,它是一个不可变类:

 * @implSpec
 * This class is immutable and thread-safe.
 *
 * @since 1.8
 */
public final class DateTimeFormatter {
	...
}

2. 不可变设计

String 类中的不可变设计

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
}

final 的使用

  • 属性用 final 修饰保证了属性是只读的,不能修改
  • 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性

保护性拷贝
以 substring 方法为例

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

可以看到,在 return 语句中,是调用了 String 的构造方法创建了一个字符串

public String(char value[], int offset, int count) {
     if (offset < 0) {
         throw new StringIndexOutOfBoundsException(offset);
     }
     if (count <= 0) {
         if (count < 0) {
             throw new StringIndexOutOfBoundsException(count);
         }
         if (offset <= value.length) {
             this.value = "".value;
             return;
         }
     }
     // Note: offset or count might be near -1>>>1.
     if (offset > value.length - count) {
         throw new StringIndexOutOfBoundsException(offset + count);
     }
     this.value = Arrays.copyOfRange(value, offset, offset+count);
 }

构造函数如下

public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

✨由上面的代码可以看到,在构造新字符串对象时,会生成新的 char[] value,对内容进行复制。这种通过创建副本对象来避免共享的手段称为【保护性拷贝(defensive copy)

3. 无状态

✨在 Web 阶段学习时,设计 Servlet 时为了保证其线程安全,都会有这样的建议,不要为 Servlet 设置成员变量,这种没有任何成员变量的类是线程安全的

由于成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】

你可能感兴趣的:(#,Java,并发编程,Java,Java,并发编程,Java,不可变)