測試方法:在多線程環境下不斷往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