java高并发6.3 线程不安全类与写法
1、线程不安全的类
常见的不安全的类:
如果一个类的对象同时可以被多个线程访问,并且你不做特殊的同步或并发处理,那么它就很容易表现出线程不安全的现象。比如抛出异常、逻辑处理错误…
下面列举一下常见的线程不安全的类及对应的线程安全类:
(1)StringBuilder与 StringBuffer
StringBuilder是线程不安全的,而StringBuffer是线程安全的。分析源码:StringBuffer的方法使用了synchronized*关键字修饰。
运行结果:
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实现了序列化接口,自己定义了序列化方法(在此不描述)
输出结果
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的测试方法 .
那么如何将其处理为线程安全的?或者说对应的线程安全类有哪些呢?接下来就涉及到我们同步容器。