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

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

2019080611330380.jpg


脑图


20190223001223379.png



概述


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

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

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


字符串拼接子之StringBuilder、StringBuffer


StringBuilder 一个可变的字符序列。它继承于AbstractStringBuilder,实现了CharSequence接口。

StringBuffer 也是继承于AbstractStringBuilder的子类;

StringBuilder和StringBuffer不同,前者是非线程安全的,后者是线程安全的。


StringBuilder (线程不安全)


2019022300284159.png

运行结果


20190223003034546.png


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


StringBuffer (线程安全)



20190223004341355.png

运行结果

20190223004401928.png


结果等于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 (线程不安全的写法)


20190223011121697.png

执行结果:表现出了异常


2019022301114571.png


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


20190223011633312.png


没有抛出异常


20190223011703534.png

线程安全,无异常


joda-time (线程安全)


20190223012241792.png


线程安全,无异常


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


20190223013405881.png

线程安全,无异常


Collections 中线程不安全的类


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

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


ArrayList (线程不安全)

20190223014303641.png


计数错误,线程不安全

20190223014316909.png

HashSet (线程不安全)


20190223014513935.png

计数错误,线程不安全

20190223014531533.png


HashMap (线程不安全)


20190223014423353.png


计数错误,线程不安全


20190223014436824.png

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


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


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


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


代码


https://github.com/yangshangwei/ConcurrencyMaster

相关文章
|
2月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
163 6
|
10天前
|
Java
【JavaEE】——多线程常用类
Callable的call方法,FutureTask类,ReentrantLock可重入锁和对比,Semaphore信号量(PV操作)CountDownLatch锁存器,
|
10天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
10天前
|
Java 调度
【JavaEE】——线程的安全问题和解决方式
【JavaEE】——线程的安全问题和解决方式。为什么多线程运行会有安全问题,解决线程安全问题的思路,synchronized关键字的运用,加锁机制,“锁竞争”,几个变式
|
10天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
10天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
2月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
2月前
|
设计模式 安全 Java
Java 多线程并发编程
Java多线程并发编程是指在Java程序中使用多个线程同时执行,以提高程序的运行效率和响应速度。通过合理管理和调度线程,可以充分利用多核处理器资源,实现高效的任务处理。本内容将介绍Java多线程的基础概念、实现方式及常见问题解决方法。
99 0
|
13天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
38 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
63 1