测试方法:在多线程环境下不断往StringBuilder
中写入字符,检测最后的StringBuilder
长度是否与写入次数相同。java
@Slf4j
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 InterruptedException {
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");
}
}
复制代码
运行结果安全
能够看到StringBuilder
的长度是小于5000的,这说明StringBuilder
是一个线程不安全的类。多线程
接下来咱们来测试一下StringBuffer
的线程安全性并发
测试代码只需把上面的StringBuilder
改为StringBuffer
便可。app
运行结果框架
屡次运行测试代码,结果始终是5000,这说明StringBuffer
是一个线程安全的类。ide
咱们点开StringBuffer
的源码看一下性能
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
复制代码
能够看到StringBuffer
的方法基本上都加了synchronized
关键字来保证线程安全。测试
在性能上StringBuilde
r要好于StringBuffer
.ui
测试方法:使用SimpleDateFormat
的parse
方法转换日期格式,抛出相应的异常。
@Slf4j
public class DateFormatExample1 {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
/** * 请求总数 */
public static int clientTotal = 5000;
/** * 同时并发执行线程数 */
public static int threadTotal = 200;
public static void main(String[] args) throws InterruptedException {
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.parse("20180208");
} catch (Exception e) {
log.error("parse exception", e);
}
}
复制代码
运行结果
抛出大量异常。所以这种写法是错误的,缘由在于SimpleDateFormat
不是线程安全的对象。
在前面的错误写法中应用堆栈封闭的思想,将SimpleDateFormat
每次声明一个新的变量来使用。
@Slf4j
public class DateFormatExample2 {
/** * 请求总数 */
public static int clientTotal = 5000;
/** * 同时并发执行线程数 */
public static int threadTotal = 200;
public static void main(String[] args) throws InterruptedException {
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);
}
}
}
复制代码
运行结果
再也不出现异常。
测试方法与以前相同
@Slf4j
public class DateFormatExample3 {
/** * 请求总数 */
public static int clientTotal = 5000;
/** * 同时并发执行线程数 */
public static int threadTotal = 200;
private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");
public static void main(String[] args) throws InterruptedException {
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(){
DateTime.parse("20180208", dateTimeFormatter).toDate();
}
}
复制代码
运行结果没有抛出异常。若是以为这样不够严谨也能够在update
方法中把每次的日期打印出来,结果必定是5000条日期。
在实际项目中更推荐使用JodaTime
中的DateTime
,它与SimpleDateFormat
的区别不单单在于线程安全方面,在实际处理方面也有更多的优点,这里就不展开来说了。
仍是以前的测试框架
@Slf4j
public class ArrayListExample {
/** * 请求总数 */
public static int clientTotal = 5000;
/** * 同时并发执行线程数 */
public static int threadTotal = 200;
private static List<Integer> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++){
final int count = i;
executorService.execute(() -> {
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e){
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}", list.size());
}
private static void update(int i){
list.add(i);
}
}
复制代码
运行结果
结果小于5000,说明ArrayList
是线程不安全的。
检测逻辑与上面相同,结果小于5000,说明HashSet
也是线程不安全的。
结果同上,线程不安全。
先检查再执行: if(condition(a)) {handle(a);}
在实际开发中若是要这样写必定要确认这个a是不是多线程共享的,若是是共享的必定要在上面加个锁或者保证这两个操做是原子性的才能够。
Written by Autu
2019.7.19