java高并发(十)线程不安全类与写法
什么是線程不安全類?
如果一個類的對象同時可以被多個線程訪問,如果不做特殊的同步與并發處理,就很容易表現出線程不安全的現象,比如拋出異常,比如邏輯處理錯誤等,這種類就是線程不安全類。
StringBuilder->StringBuffer
@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 (InterruptedException e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();log.info("size:{}", stringBuilder.length());}public static void update() {stringBuilder.append("1");} }輸出結果與我們預期的不一致。StringBuilder是一個線程不安全的類。?
我們將StringBuilder換成StringBuffer,可以得到預期的效果。說明StringBuffer是線程安全的。
查看StringBuffer的append方法,發現這個方法與其他方法前添加了synchronized關鍵字。
StringBuffer因為使用了synchronized關鍵字,因此在使用的時候會有性能損耗的,因此在做字符串拼接時涉及到多線程可以考慮StringBuffer來處理。
但是很多時候,我們往往在一個方法里面做字符串拼接單獨,定義一個StringBuilder變量就可以了。因為在一個方法內部定義局部變量時屬于堆棧封閉,這時只有單個線程可以操作對象,不涉及到線程安全問題了。
SimpleDateFormat -> JodaTime?
@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 (InterruptedException e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();}public static void update() {try {simpleDateFormat.parse("20190729");} catch (ParseException e) {e.printStackTrace();log.error("parse Exception" + e);}}}運行時,會拋出異常:
Exception in thread "pool-1-thread-3" java.lang.NumberFormatException: For input string: "E.177"at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)at java.lang.Double.parseDouble(Double.java:538)at java.text.DigitList.getDouble(DigitList.java:169)at java.text.DecimalFormat.parse(DecimalFormat.java:2056)at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867)at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)at java.text.DateFormat.parse(DateFormat.java:364)at com.vincent.example.commonUnsafe.DateFormatExample1.update(DateFormatExample1.java:50)at com.vincent.example.commonUnsafe.DateFormatExample1.lambda$main$0(DateFormatExample1.java:34)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:745)這是線程不安全的,simpleDateFormat不是一個線程安全的類,一種解決辦法是將SimpleDateFormat simpleDateFormat = new SimpleDateFormat()放到方法內,封閉堆棧,修改update方法如下:
public static void update() {try {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");simpleDateFormat.parse("20190729");} catch (ParseException e) {e.printStackTrace();log.error("parse Exception" + e);}}JodaTime是線程安全的:
@Slf4j @ThreadSafe 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++) {final int count = i;executorService.execute(() ->{try {semaphore.acquire();update(count);semaphore.release();} catch (InterruptedException e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();}public static void update(int i) {log.info("{}, {}", i, DateTime.parse("20190729",dateTimeFormatter).toDate());}}ArrayList,HashSet,HashMap等Collections
通常我們使用這些集合類時他們的對象通常聲明在方法里面作為局部變量來使用,很少觸發線程不安全的問題,但是一旦定義成static的時候而且多個線程可以進行修改的時候就會容器出問題。例如下面的代碼:
@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 (InterruptedException e) {log.error("exception", e);}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();log.info("size:{}",list.size()) ;}public static void update(int i) {list.add(i);}}輸出結果不是我們所預期的。
同樣適用HashSet,HashMap也無法輸出正確的結果。這些都是線程不安全的。
后面會介紹這些集合對應的線程安全類。
先檢查在執行 if(condition(a)) {handle(a);}
為什么這種寫法是線程不安全的?假設a是線程安全的類,即使if(condition(a))是線程安全的操作,handle(a)也是線程安全的,但是兩個結合起來就不是線程安全的了,并不是原子性的。
Atomic類在自增的時候,底層實現是通過CAS原理來保證原子性的跟新。
實際過程中,如果遇到這種情況要判斷一個對象是否滿足某個條件,然后做某個操作,一定先要考慮這個對象是否多線程共享的,如果是多線程共享的一定要在上面加鎖,或者保證操作是原子性的才可以。否則會觸發線程不安全的。
總結
以上是生活随笔為你收集整理的java高并发(十)线程不安全类与写法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java高并发(九)线程封闭
- 下一篇: java高并发(十一)同步容器