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

Posted 爱上口袋的天空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程-线程安全策略之常见的线程不安全类相关的知识,希望对你有一定的参考价值。

脑图

概述 

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

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

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

字符串拼接之StringBuilder、StringBuffer

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

StringBuilder (线程不安全)

运行结果

 

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

StringBuffer (线程安全)

运行结果

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

 小结

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

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

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

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

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

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

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

SimpleDateFormat (线程不安全的写法)

执行结果:表现出了异常
 

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

 

没有抛出异常

 线程安全,无异常

 joda-time (线程安全)

线程安全,无异常

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

 

线程安全,无异常

Collections 中线程不安全的类

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

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

 ArrayList (线程不安全)

计数错误,线程不安全

 

HashSet (线程不安全)

 

 计数错误,线程不安全

HashMap (线程不安全)

 

计数错误,线程不安全

 

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

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

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

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

以上是关于并发编程-线程安全策略之常见的线程不安全类的主要内容,如果未能解决你的问题,请参考以下文章

并发编程-线程安全策略之不可变对象

并发编程-线程安全策略之线程封闭

JUC并发编程 -- 常见的线程安全类 & 线程安全类方法的组合 & 不可变类线程安全性

并发编程-线程安全策略之两种类型的同步容器

Java并发多线程编程——集合类线程不安全之HashMap的示例及解决方案

并发编程:线程安全策略