文章目录

  • 脑图
  • 概述
  • 字符串拼接子之StringBuilder、StringBuffer
  • StringBuilder (线程不安全)
  • StringBuffer (线程安全)
  • 小结
  • 时间相关的类 SimpleDateFormat、第三方库joda-time、JDK8提供的类
  • SimpleDateFormat (线程不安全的写法)
  • SimpleDateFormat (线程安全的写法-堆栈封闭)
  • joda-time (线程安全)
  • JDK8的时间处理类(线程安全)
  • Collections 中线程不安全的类
  • ArrayList (线程不安全)
  • HashSet (线程不安全)
  • HashMap (线程不安全)
  • 注意事项 (先检查后执行-- 线程不安全)
  • 代码


并发编程-12线程安全策略之常见的线程不安全类_局部变量

脑图

并发编程-12线程安全策略之常见的线程不安全类_线程安全_02


概述

前两篇博客,我们说了 通过 不可变变量​ 和 线程封闭 这两种方式来实现线程安全。

这里我们来介绍下很常见的线程不安全的类

所谓线程不安全的类,是指一个类的实例对象可以同时被多个线程访问,如果不做同步或线程安全的处理,就会表现出线程不安全的行为,比如逻辑处理错误、抛出异常等。


字符串拼接子之StringBuilder、StringBuffer

  • ​StringBuilder​​​ 一个可变的字符序列。它继承于​​AbstractStringBuilder​​​,实现了​​CharSequence​​接口。
  • ​StringBuffer​​​ 也是继承于​​AbstractStringBuilder​​的子类;
  • ​StringBuilder​​​和​​StringBuffer​​不同,前者是非线程安全的,后者是线程安全的。

StringBuilder (线程不安全)

并发编程-12线程安全策略之常见的线程不安全类_线程不安全的类_03

运行结果

并发编程-12线程安全策略之常见的线程不安全类_堆栈_04

结果不等于5000,在多线程情况下,StringBuild是线程不安全的.


StringBuffer (线程安全)

并发编程-12线程安全策略之常见的线程不安全类_线程不安全的类_05

运行结果

并发编程-12线程安全策略之常见的线程不安全类_线程安全_06

结果等于5000,在多线程情况下,StringBuffer是线程安全的.


小结

既然StringBuffer是线程安全的, 那为何JDK还要提供StringBuilder呢?

StringBuffer之所以是线程安全的原因是几乎所有的方法都加了synchronized关键字,所以是线程安全的。 synchronized 同一时间只能有一个线程访问,所以性能会相对较差。

在上篇博文 并发编程-11线程安全策略之线程封闭中,我们了解到 线程封闭可以确保线程安全,其中线程封闭的一种实现方式时堆栈封闭,说白了就是局部变量

所以推荐在堆栈封闭等线程安全的环境下(方法中的局部变量)应该首先选用StringBuilder。


时间相关的类 SimpleDateFormat、第三方库joda-time、JDK8提供的类

SimpleDateFormat 的实例对象在多线程共享使用的时候会抛出转换异常,正确的使用方法应该是采用堆栈封闭,将其作为方法内的局部变量而不是全局变量,在每次调用方法的时候才去创建一个SimpleDateFormat实例对象,这样利用堆栈封闭就不会出现并发问题

另一种方式是使用第三方库joda-time的DateTimeFormatter类 或者JDK8新提供的类 : 不可变类且线程安全 LocalDate 、java.time.LocalTime 和LocaldateTime 新的Date和Time类DateTimeFormatter


SimpleDateFormat (线程不安全的写法)

并发编程-12线程安全策略之常见的线程不安全类_线程安全_07

执行结果:表现出了异常

并发编程-12线程安全策略之常见的线程不安全类_堆栈_08


SimpleDateFormat (线程安全的写法-堆栈封闭)

并发编程-12线程安全策略之常见的线程不安全类_线程不安全的类_09

没有抛出异常

并发编程-12线程安全策略之常见的线程不安全类_线程安全_10

线程安全,无异常


joda-time (线程安全)

并发编程-12线程安全策略之常见的线程不安全类_线程安全_11

线程安全,无异常


JDK8的时间处理类(线程安全)

并发编程-12线程安全策略之常见的线程不安全类_局部变量_12

线程安全,无异常


Collections 中线程不安全的类

通常情况下,我们都是在方法内部作为局部变量使用这些集合类,很少会触发线程不安全的问题。

如果在某些情况下定义成static,而且多个线程可以修改的时候就容易出现多线程不安全的问题。


ArrayList (线程不安全)

并发编程-12线程安全策略之常见的线程不安全类_线程不安全的类_13

计数错误,线程不安全

并发编程-12线程安全策略之常见的线程不安全类_堆栈_14


HashSet (线程不安全)

并发编程-12线程安全策略之常见的线程不安全类_线程安全_15

计数错误,线程不安全

并发编程-12线程安全策略之常见的线程不安全类_堆栈_16


HashMap (线程不安全)

并发编程-12线程安全策略之常见的线程不安全类_局部变量_17

计数错误,线程不安全

并发编程-12线程安全策略之常见的线程不安全类_局部变量_18


注意事项 (先检查后执行-- 线程不安全)

有一种写法需要注意,即便是线程安全的对象,在这种写法下也可能会出现线程不安全的行为,这种写法就是先检查后执行

if(condition(a)){
handle(a);
}

在这个操作里,可能会有两个线程同时通过if的判断,然后去执行了处理方法,那么就会出现两个线程同时操作一个对象,从而出现线程不安全的行为。这种写法导致线程不安全的主要原因是因为这里分成了两步操作,这个过程是非原子性的,所以就会出现线程不安全的问题。


代码

https://github.com/yangshangwei/ConcurrencyMaster