您好,登錄后才能下訂單哦!
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 (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變量就可以了。因為在一個方法內部定義局部變量時屬于堆棧封閉,這時只有單個線程可以操作對象,不涉及到線程安全問題了。
@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()); } }
通常我們使用這些集合類時他們的對象通常聲明在方法里面作為局部變量來使用,很少觸發線程不安全的問題,但是一旦定義成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也無法輸出正確的結果。這些都是線程不安全的。
后面會介紹這些集合對應的線程安全類。
為什么這種寫法是線程不安全的?假設a是線程安全的類,即使if(condition(a))是線程安全的操作,handle(a)也是線程安全的,但是兩個結合起來就不是線程安全的了,并不是原子性的。
Atomic類在自增的時候,底層實現是通過CAS原理來保證原子性的跟新。
實際過程中,如果遇到這種情況要判斷一個對象是否滿足某個條件,然后做某個操作,一定先要考慮這個對象是否多線程共享的,如果是多線程共享的一定要在上面加鎖,或者保證操作是原子性的才可以。否則會觸發線程不安全的。
看完上述內容,你們掌握java高并發中線程不安全類與寫法是什么的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。