java高并发6.3 线程不安全类与写法

1、线程不安全的类

常见的不安全的类:

java高并发6.3 线程不安全类与写法

如果一个类的对象同时可以被多个线程访问,并且你不做特殊的同步或并发处理,那么它就很容易表现出线程不安全的现象。比如抛出异常、逻辑处理错误…

下面列举一下常见的线程不安全的类及对应的线程安全类:

(1)StringBuilder与 StringBuffer

StringBuilder是线程不安全的,而StringBuffer是线程安全的。分析源码:StringBuffer的方法使用了synchronized*关键字修饰。

java高并发6.3 线程不安全类与写法

运行结果:

count<5000 StringBuilder是线程不安全的类

如果把StringBuilder 改成---->StringBuffer (实现的时候加了synchronize关键字)其他不变 . 则count输出5000 (线程安全)

StringBuffer (实现方法的时候加了synchronize关键字)其所以方法同一个时间只有一个线程能够调用, 在性能上是有损耗的

____________________________________________________________________________

(2)SimpleDateFormat 与 jodatime插件

SimpleDateFormat 类在处理时间的时候,如下写法是线程不安全的:

还是调用上面的例子, 只是实例化的方法不一样

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");

//线程调用方法

private static void update() {

    try {

        simpleDateFormat.parse("20180208");

    } catch (Exception e) {

        log.error("parse exception", e);

    }

}

但是我们可以变换其为线程安全的写法:在每次转换的时候使用线程封闭,新建变量

private static void update() {

    try {

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");

        simpleDateFormat.parse("20180208");

    } catch (Exception e) {

        log.error("parse exception", e);

    }

}

另外我们也可以使用jodatime插件来转换时间:其可以保证线程安全性

Joda 类具有不可变性,因此它们的实例无法被修改。(不可变类的一个优点就是它们是线程安全的)

private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");



private static void update(int i) {

    log.info("{}, {}", i, DateTime.parse("20180208", dateTimeFormatter).toDate());

}

(3)ArrayList,HashSet,HashMap 等Collection类*

像ArrayList,HashSet,HashMap 等Collection类均是线程不安全的,通常我们使用它们的时候他们的对象都是声明在方法里面, 都是局部变量 , 所以少出现线程不安全的问题

 

我们以ArrayList举例分析一下源码:

1、ArrayList的基本属性:

在声明时使用了transient 关键字,此关键字意为在采用Java默认的序列化机制的时候,被该关键字修饰的属性不会被序列化。而ArrayList实现了序列化接口,自己定义了序列化方法(在此不描述)

 

java高并发6.3 线程不安全类与写法

输出结果

size < 5000 

所以是线程不安全的

总结:

ArrayList每次对内容进行插入操作的时候,都会做扩容处理,这是ArrayList的优点(无容量的限制),同时也是缺点,线程不安全。(以下例子取材于鱼笑笑博客)

一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:

  • 在 Items[Size] 的位置存放此元素;

  • 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

HashSet和HashMap同样是线程不安全的,具体就不演示了, 类似于ArrayList的测试方法 . 

那么如何将其处理为线程安全的?或者说对应的线程安全类有哪些呢?接下来就涉及到我们同步容器。