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

得意时要看淡,失意时要看开。不论得意失意,切莫大意;不论成功失败,切莫止步。志得意满时,需要的是淡然,给自己留一条退路;失意落魄时,需要的是泰然,给自己觅一条出路《Java 并发编程》共享模型之不可变,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

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

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

🚀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 设置成员变量,这种没有任何成员变量的类是线程安全的

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/156978.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!