概述
线程不安全类:如果一个类的对象同时被多个线程访问,若不做相应的同步或并发处理,容易出现线程不安全的现象,比如:抛出异常、逻辑处理错误等。
StringBiulder
看一个例子:(借助之前的例子结构)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 4j
public class StringExample1 {
public static int clientTotal = 5000;
public static int threadTotal = 200;
public static StringBuilder stringBuilder = new StringBuilder();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
update();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}", stringBuilder.length());
}
private static void update() {
stringBuilder.append("1");
}
}
结果分析:
- 多次运行,结果几乎未达5000,显然StringBuilder是非线程安全的。
- 通过定义stringBuilder对象,核心方法为update() -> 每次拼接一个字符串,最后取其长度length。
StringBuffer
例子代码结构跟上面一样,只需将StringBiulder换为StringBuffer,且其两者方法名相同。
运行结果:
多次运行,结果均为5000。StringBuffer是线程安全的!
查看StringBuffer源码
1 | @Override |
1 | public final class StringBuffer |
1 |
|
源码分析:
- 几乎所有复写的方法都有toStringCache变量。为对象方便转为String类型的字段,调用Arrays.copyOfRange(value, 0, count),return new String(toStringCache, true):其中该构造方法是String包私有的构造方法,以确保数值分享的效率。
- StringBuffer继承自AbstractStringBuilder,并几乎重写了所有继承来的方法。调用父辈super的append方法,即AbstractStringBuilder的方法。且StringBuffer对象一经修改,toStringCache清空为null。类似String包装类的对象,避免多线程并发问题。(以后细说String包装类…)
- 为了线程安全,几乎所有复写的方法都用synchronized进行标识,即使效率较低。
- StringBiulder性能好,但不适用于多线程。但适用于场景为方法内的局部变量操作(上篇线程封闭的手记中说到:隐式的堆栈封闭),线程安全且性能较好。
SimpleDateFormate
Java提供的供日期转换的类。该例子结构仍然和上述例子相同。只需定义一个simpleDateFormat实例,核心方法换为parse(xxx:日期语句)
运行结果:
出现异常:parse exception -- java.lang.NumberFormatException: multiple points
非常简单,说明了该日期转换方法是非线程间安全的。
正确写法:(通过堆栈封闭->声明为核心方法内的局部变量,即每次声明一个新的对象进行调用)
仍然是之前的测试结构,但还是贴出来看看吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 4j
public class DateFormatExample2 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
update();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
}
private static void update() {
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
simpleDateFormat.parse("20180208");
} catch (Exception e) {
log.error("parse exception", e);
}
}
}
运行结果: 多次运行,不会报错!是线程安全!
Joda Time
该类本质上并不属于Java提供。需引入jar包。例子仍然是之前的测试结构。只不过日志输出了次数及当时日期
1
2
3
4 private static void update(int i) {
//转换完后DateTime,调用toDate()转为Date对象。
log.info("{}, {}", i == 4999 ? i+"--------------------------" : i, DateTime.parse("20180728", dateTimeFormatter).toDate());
}
运行结果:
由于调用是多并发的,调用次序是乱序,但总数一定!
由于i是从0到4999,即当i = 4999时说明已运行慢5000个,即线程安全!
图片截图中字体小了,可以点开看~
(后接该手记part-2……)