1、安全的發布對象,有一種對象只要發布了,就是安全的,就是不可變對象。一個類的對象是不可變的對象,不可變對象必須滿足三個條件。
1)、第一個是對象創建以后其狀態就不能修改。
2)、第二個是對象所有域都是final類型的。
3)、第三個是對象是正確創建的(在對象創建期間,this引用沒有逸出)。
3、創建不可變的對象,可以參考String類的哦。
答:可以采用的方式有,將類聲明為final類型的,就不能被繼承了;將所有的成員聲明為私有的,這樣就不能直接訪問這些成員;對變量不提供set方法,將所有可變的成員聲明為final類型的,這樣只能對他們賦值一次的;通過構造器初始化所有成員,進行深度拷貝;在get方法中不直接返回方法的本身,而是克隆對象,並返回對象的拷貝。
4、final關鍵字:修飾類、修飾方法、修飾變量。
1)、修飾類(該類不能被繼承,比如Stirng、Integer、Long等等類)。
2)、修飾方法(修飾方法不能重寫。鎖定方法不被繼承類修改)。
3)、修飾變量(修飾基本數據類型變量,該基本數據類型變量不可變。修飾引用數據類型變量,則在對其初始化以后不能讓他再指向另外其他對象的。修飾基本數據類型變量,修飾引用類型變量)。
final類里面的成員變量可以根據需要設置成final類型的,final類里面的成員方法都會被隱式指定為final方法的。
4.1、final 修飾引用數據類型變量,則在對其初始化以后不能讓他再指向另外其他對象的。但是其值是可以進行修改的。
1 package com.bie.concurrency.example.immutable; 2 3 import java.util.Map; 4 5 import com.bie.concurrency.annoations.NotThreadSafe; 6 import com.google.common.collect.Maps; 7 8 import lombok.extern.slf4j.Slf4j; 9 10 /** 11 * 12 * 13 * @Title: ImmutableExample1.java 14 * @Package com.bie.concurrency.example.immutable 15 * @Description: TODO 16 * @author biehl 17 * @date 2020年1月8日 18 * @version V1.0 19 * 20 * 1、final 修飾引用數據類型變量,則在對其初始化以后不能讓他再指向另外其他對象的。但是其值是可以進行修改的。 21 */ 22 @Slf4j 23 @NotThreadSafe // 線程不安全的。 24 public class ImmutableExample1 { 25 26 private final static Integer a = 1; 27 private final static String b = "2"; 28 private final static Map<Integer, Integer> map = Maps.newHashMap(); 29 30 static { 31 // final 修飾基本數據類型變量,該基本數據類型變量不可變。 32 // a = 2; 33 // b = "3"; 34 35 // final 修飾引用數據類型變量,則在對其初始化以后不能讓他再指向另外其他對象的。但是其值是可以進行修改的。 36 // map = Maps.newHashMap(); 37 38 map.put(1, 2); 39 map.put(3, 4); 40 map.put(5, 6); 41 } 42 43 // 如果參數也是final類型的,那么這個參數不可以進行修改的。 44 private void test(final int a) { 45 // a = 1; 46 } 47 48 public static void main(String[] args) { 49 // final 修飾引用數據類型變量,則在對其初始化以后不能讓他再指向另外其他對象的。 50 // 但是其值是可以進行修改的。 51 map.put(1, 3); 52 log.info("{}", map.get(1)); 53 } 54 55 }
4.2、Collections.unmodifiableXXX : Collection、List、Set、Map。不允許修改的方法。
1 package com.bie.concurrency.example.immutable; 2 3 import java.util.Collection; 4 import java.util.Collections; 5 import java.util.Iterator; 6 import java.util.Map; 7 import java.util.Map.Entry; 8 import java.util.Set; 9 10 import com.bie.concurrency.annoations.ThreadSafe; 11 import com.google.common.collect.Maps; 12 13 import lombok.extern.slf4j.Slf4j; 14 15 /** 16 * 17 * 18 * @Title: ImmutableExample1.java 19 * @Package com.bie.concurrency.example.immutable 20 * @Description: TODO 21 * @author biehl 22 * @date 2020年1月8日 23 * @version V1.0 24 * 25 * 26 * 1、Collections.unmodifiableXXX : Collection、List、Set、Map。不允許修改的方法。 27 * 28 * 29 */ 30 @Slf4j 31 @ThreadSafe // 線程安全的。 32 public class ImmutableExample2 { 33 34 private static Map<Integer, Integer> map = Maps.newHashMap(); 35 36 static { 37 map.put(1, 2); 38 map.put(3, 4); 39 map.put(5, 6); 40 map = Collections.unmodifiableMap(map); 41 } 42 43 public static void main(String[] args) { 44 for (Integer key : map.keySet()) { 45 System.out.println("key : " + key + ", value : " + map.get(key)); 46 } 47 48 System.out.println("================================================"); 49 50 Iterator<Entry<Integer, Integer>> iterator = map.entrySet().iterator(); 51 while (iterator.hasNext()) { 52 Entry<Integer, Integer> next = iterator.next(); 53 System.out.println("key : " + next.getKey() + ", value : " + next.getValue()); 54 } 55 56 System.out.println("================================================"); 57 58 Set<Entry<Integer, Integer>> entrySet = map.entrySet(); 59 for (Map.Entry<Integer, Integer> entry : map.entrySet()) { 60 System.out.println("key : " + entry.getKey() + ", value : " + entry.getValue()); 61 } 62 63 System.out.println("================================================"); 64 65 Collection<Integer> values = map.values(); 66 for (Integer value : map.values()) { 67 System.out.println(value); 68 } 69 70 System.out.println("================================================"); 71 72 map.put(1, 3); // 拋出異常 73 log.info("{}", map.get(1)); 74 } 75 76 }
4.3、ImmutableXXX : Collection、List、Set、Map。谷歌提高的不允許修改的方法。
1 package com.bie.concurrency.example.immutable; 2 3 import com.bie.concurrency.annoations.ThreadSafe; 4 import com.google.common.collect.ImmutableList; 5 import com.google.common.collect.ImmutableMap; 6 import com.google.common.collect.ImmutableSet; 7 8 import lombok.extern.slf4j.Slf4j; 9 10 /** 11 * 12 * 13 * @Title: ImmutableExample1.java 14 * @Package com.bie.concurrency.example.immutable 15 * @Description: TODO 16 * @author biehl 17 * @date 2020年1月8日 18 * @version V1.0 19 * 20 * 21 * 1、ImmutableXXX : Collection、List、Set、Map。谷歌提高的不允許修改的方法。 22 * 23 * 24 */ 25 @Slf4j 26 @ThreadSafe // 線程安全的。 27 public class ImmutableExample3 { 28 29 private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); 30 31 private final static ImmutableSet<Integer> set = ImmutableSet.copyOf(list); 32 33 private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 11, 2, 22, 3, 33, 4, 44, 5, 55); 34 35 private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder().put(1, 11) 36 .put(2, 22).put(3, 33).put(4, 44).put(5, 55).put(6, 66).put(7, 77).build(); 37 38 public static void main(String[] args) { 39 System.out.println(list.toString()); 40 System.out.println(set.toString()); 41 System.out.println(map.toString()); 42 System.out.println(map2.toString()); 43 } 44 }
5、線程封閉,如何實現線程封閉呢?
不可變對象,通過在某些情況下,通過將不會修改的類對象,設計成不可變對象,來讓對象在多個線程之間保證是線程安全的,歸根到底,是躲避了並發這個問題,因為不能讓多個線程在同一時間同時訪問同一線程。避免並發除了設計成不可變對象,還可以使用線程封閉,其實就是將對象封裝到一個線程里面,只有一個線程可以看到這個對象,那么這個對象就算不是線程安全的,也不會出現任何安全方面的問題了,因為他們只能在一個線程里面進行訪問。
第一種實現線程封閉的方法,堆棧封閉:局部變量,無並發問題哦,可以深思這句話的呢。簡單的說,堆棧封閉就是局部變量,多個線程訪問一個方法的時候呢,方法中的局部變量都會被拷貝一份到線程的棧中,所以呢,局部變量是不會被線程所共享的,因此也不會出現並發問題。所以可以使用局部變量的時候,就不用全局變量哦,全局變量容易引起並發問題,是全局變量哦,不是全局常量哈,注意區分。
第二種實現線程封閉的方法,ThreadLocal線程封閉,特別好的線程封閉方法。ThreadLocal內部維護了一個Map,map的key是每個線程的名稱,而map的值就是我們要封閉的對象。每個線程中的對象都對應一個map中的值,也就是說,ThreadLocal利用Map實現了線程封閉的哦。
1 package com.bie.concurrency.example.threadLocal; 2 3 /** 4 * 5 * 6 * @Title: RequestHolder.java 7 * @Package com.bie.concurrency.example.threadLocal 8 * @Description: TODO 9 * @author biehl 10 * @date 2020年1月8日 11 * @version V1.0 12 * 13 * 1、線程封閉。ThreadLocal的實現。 14 */ 15 public class RequestHolder { 16 17 private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>(); 18 19 /** 20 * 向ThreadLocal中設置一個id的值 21 * 22 * @param id 23 */ 24 public static void add(Long id) { 25 requestHolder.set(id); 26 } 27 28 /** 29 * 從ThreadLocal中獲取id的值 30 * 31 * @return 32 */ 33 public static Long getId() { 34 return requestHolder.get(); 35 } 36 37 /** 38 * 刪除ThreadLocal里面的所有值 39 */ 40 public static void remove() { 41 requestHolder.remove(); 42 } 43 }
6、安全發布對象的方法,圍繞着安全發布對象,寫了不可變對象,線程封閉,帶來的線程安全,下面說一下線程不安全的類與寫法。線程不安全的類就是一個類的對象同時被多個線程訪問,如果不做特殊同步或者並發處理,就很容易表現出線程不安全的現象,比如拋出異常或者邏輯處理錯誤,就被成為線程不安全的類。
6.1、StringBuilder線程不安全,但是效率高、StringBuffer線程安全的,因為方法前面加了synchronized關鍵字的,同一時間只能有一個線程進行訪問,StringBuffer效率相對於StringBuilder低,性能有所損耗。
StringBuilder線程不安全,多線程測試,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.util.concurrent.CountDownLatch; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.Semaphore; 7 8 import com.bie.concurrency.annoations.NotThreadSafe; 9 10 import lombok.extern.slf4j.Slf4j; 11 12 /** 13 * 14 * 15 * @Title: StringBuilderExample1.java 16 * @Package com.bie.concurrency.example.commonUnsafe 17 * @Description: TODO 18 * @author biehl 19 * @date 2020年1月9日 20 * @version V1.0 21 * 22 */ 23 @Slf4j 24 @NotThreadSafe // 由於每次結果不一致,所以是線程不安全的類。可以使用此程序進行並發測試。 25 public class StringBuilderExample1 { 26 27 public static int clientTotal = 5000;// 5000個請求,請求總數 28 29 public static int threadTotal = 200;// 允許同時並發執行的線程數目 30 31 // StringBuilder線程不安全的 32 private static StringBuilder sb = new StringBuilder(); 33 34 private static void update() { 35 sb.append("a"); 36 } 37 38 public static void main(String[] args) { 39 // 定義線程池 40 ExecutorService executorService = Executors.newCachedThreadPool(); 41 // 定義信號量,信號量里面需要定義允許並發的數量 42 final Semaphore semaphore = new Semaphore(threadTotal); 43 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 44 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 45 // 放入請求操作 46 for (int i = 0; i < clientTotal; i++) { 47 // 所有請求放入到線程池結果中 48 executorService.execute(() -> { 49 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 50 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 51 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 52 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 53 try { 54 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 55 semaphore.acquire(); 56 // 核心執行方法。 57 update(); 58 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 59 semaphore.release(); 60 } catch (InterruptedException e) { 61 e.printStackTrace(); 62 } 63 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 64 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 65 // 執行countDown()方法計數器減一。 66 countDownLatch.countDown(); 67 }); 68 } 69 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 70 try { 71 // 調用await()方法當前進程進入等待狀態。 72 countDownLatch.await(); 73 } catch (InterruptedException e) { 74 e.printStackTrace(); 75 } 76 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 77 executorService.shutdown(); 78 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 79 log.info("sb:{}", sb.length()); 80 81 } 82 }
StringBuffer線程安全,多線程測試,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.util.concurrent.CountDownLatch; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.Semaphore; 7 8 import com.bie.concurrency.annoations.ThreadSafe; 9 10 import lombok.extern.slf4j.Slf4j; 11 12 /** 13 * 14 * 15 * @Title: StringBuilderExample1.java 16 * @Package com.bie.concurrency.example.commonUnsafe 17 * @Description: TODO 18 * @author biehl 19 * @date 2020年1月9日 20 * @version V1.0 21 * 22 */ 23 @Slf4j 24 @ThreadSafe // 由於每次結果一致,所以是線程安全的類。可以使用此程序進行並發測試。 25 public class StringBufferExample2 { 26 27 public static int clientTotal = 5000;// 5000個請求,請求總數 28 29 public static int threadTotal = 200;// 允許同時並發執行的線程數目 30 31 // StringBuffer線程安全的 32 private static StringBuffer sb = new StringBuffer(); 33 34 private static void update() { 35 sb.append("a"); 36 } 37 38 public static void main(String[] args) { 39 // 定義線程池 40 ExecutorService executorService = Executors.newCachedThreadPool(); 41 // 定義信號量,信號量里面需要定義允許並發的數量 42 final Semaphore semaphore = new Semaphore(threadTotal); 43 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 44 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 45 // 放入請求操作 46 for (int i = 0; i < clientTotal; i++) { 47 // 所有請求放入到線程池結果中 48 executorService.execute(() -> { 49 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 50 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 51 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 52 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 53 try { 54 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 55 semaphore.acquire(); 56 // 核心執行方法。 57 update(); 58 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 59 semaphore.release(); 60 } catch (InterruptedException e) { 61 e.printStackTrace(); 62 } 63 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 64 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 65 // 執行countDown()方法計數器減一。 66 countDownLatch.countDown(); 67 }); 68 } 69 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 70 try { 71 // 調用await()方法當前進程進入等待狀態。 72 countDownLatch.await(); 73 } catch (InterruptedException e) { 74 e.printStackTrace(); 75 } 76 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 77 executorService.shutdown(); 78 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 79 log.info("sb:{}", sb.length()); 80 81 } 82 }
6.2、時間格式化SimpleDateFormat,線程不安全。時間格式化JodaTime線程安全的。
時間格式化SimpleDateFormat,線程不安全,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.text.SimpleDateFormat; 4 import java.util.concurrent.CountDownLatch; 5 import java.util.concurrent.ExecutorService; 6 import java.util.concurrent.Executors; 7 import java.util.concurrent.Semaphore; 8 9 import com.bie.concurrency.annoations.NotThreadSafe; 10 11 import lombok.extern.slf4j.Slf4j; 12 13 /** 14 * 15 * 16 * @Title: DateFormatExample1.java 17 * @Package com.bie.concurrency.example.commonUnsafe 18 * @Description: TODO 19 * @author biehl 20 * @date 2020年1月9日 21 * @version V1.0 22 * 23 */ 24 @Slf4j 25 @NotThreadSafe // 由於每次結果不一致,所以是線程不安全的類。可以使用此程序進行並發測試。 26 public class DateFormatExample1 { 27 28 public static int clientTotal = 5000;// 5000個請求,請求總數 29 30 public static int threadTotal = 200;// 允許同時並發執行的線程數目 31 32 // SimpleDateFormat是線程不安全的 33 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); 34 35 private static void update() { 36 try { 37 simpleDateFormat.parse("20200109"); 38 } catch (Exception e) { 39 log.error("parse exception", e); 40 } 41 } 42 43 public static void main(String[] args) { 44 // 定義線程池 45 ExecutorService executorService = Executors.newCachedThreadPool(); 46 // 定義信號量,信號量里面需要定義允許並發的數量 47 final Semaphore semaphore = new Semaphore(threadTotal); 48 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 49 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 50 // 放入請求操作 51 for (int i = 0; i < clientTotal; i++) { 52 // 所有請求放入到線程池結果中 53 executorService.execute(() -> { 54 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 55 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 56 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 57 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 58 try { 59 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 60 semaphore.acquire(); 61 // 核心執行方法。 62 update(); 63 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 64 semaphore.release(); 65 } catch (InterruptedException e) { 66 e.printStackTrace(); 67 } 68 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 69 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 70 // 執行countDown()方法計數器減一。 71 countDownLatch.countDown(); 72 }); 73 } 74 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 75 try { 76 // 調用await()方法當前進程進入等待狀態。 77 countDownLatch.await(); 78 } catch (InterruptedException e) { 79 e.printStackTrace(); 80 } 81 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 82 executorService.shutdown(); 83 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 84 log.info("simpleDateFormat:{}", simpleDateFormat); 85 86 } 87 }
堆棧封閉:局部變量,無並發問題哦。將SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");定義為局部變量。
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.text.SimpleDateFormat; 4 import java.util.concurrent.CountDownLatch; 5 import java.util.concurrent.ExecutorService; 6 import java.util.concurrent.Executors; 7 import java.util.concurrent.Semaphore; 8 9 import com.bie.concurrency.annoations.ThreadSafe; 10 11 import lombok.extern.slf4j.Slf4j; 12 13 /** 14 * 15 * 16 * @Title: DateFormatExample1.java 17 * @Package com.bie.concurrency.example.commonUnsafe 18 * @Description: TODO 19 * @author biehl 20 * @date 2020年1月9日 21 * @version V1.0 22 * 23 */ 24 @Slf4j 25 @ThreadSafe // 由於每次結果一致,所以是線程安全的類。可以使用此程序進行並發測試。 26 public class DateFormatExample1 { 27 28 public static int clientTotal = 5000;// 5000個請求,請求總數 29 30 public static int threadTotal = 200;// 允許同時並發執行的線程數目 31 32 private static void update() { 33 try { 34 // 堆棧封閉:局部變量,無並發問題哦, 35 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); 36 simpleDateFormat.parse("20200109"); 37 } catch (Exception e) { 38 log.error("parse exception", e); 39 } 40 } 41 42 public static void main(String[] args) { 43 // 定義線程池 44 ExecutorService executorService = Executors.newCachedThreadPool(); 45 // 定義信號量,信號量里面需要定義允許並發的數量 46 final Semaphore semaphore = new Semaphore(threadTotal); 47 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 48 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 49 // 放入請求操作 50 for (int i = 0; i < clientTotal; i++) { 51 // 所有請求放入到線程池結果中 52 executorService.execute(() -> { 53 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 54 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 55 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 56 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 57 try { 58 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 59 semaphore.acquire(); 60 // 核心執行方法。 61 update(); 62 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 63 semaphore.release(); 64 } catch (InterruptedException e) { 65 e.printStackTrace(); 66 } 67 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 68 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 69 // 執行countDown()方法計數器減一。 70 countDownLatch.countDown(); 71 }); 72 } 73 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 74 try { 75 // 調用await()方法當前進程進入等待狀態。 76 countDownLatch.await(); 77 } catch (InterruptedException e) { 78 e.printStackTrace(); 79 } 80 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 81 executorService.shutdown(); 82 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 83 84 } 85 }
時間格式化JodaTime,線程安全的,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.util.concurrent.CountDownLatch; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.Semaphore; 7 8 import org.joda.time.DateTime; 9 import org.joda.time.format.DateTimeFormat; 10 import org.joda.time.format.DateTimeFormatter; 11 12 import com.bie.concurrency.annoations.ThreadSafe; 13 14 import lombok.extern.slf4j.Slf4j; 15 16 /** 17 * 18 * 19 * @Title: DateFormatExample1.java 20 * @Package com.bie.concurrency.example.commonUnsafe 21 * @Description: TODO 22 * @author biehl 23 * @date 2020年1月9日 24 * @version V1.0 25 * 26 * 線程安全的,推薦使用joda 27 */ 28 @Slf4j 29 @ThreadSafe // 由於每次結果一致,所以是線程安全的類。可以使用此程序進行並發測試。 30 public class DateFormatExample3 { 31 32 public static int clientTotal = 5000;// 5000個請求,請求總數 33 34 public static int threadTotal = 200;// 允許同時並發執行的線程數目 35 36 private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); 37 38 private static void update(int count) { 39 try { 40 DateTime.parse("20200109", dateTimeFormatter).toDate(); 41 log.info("{}, {}", count, DateTime.parse("20180208", dateTimeFormatter).toDate()); 42 } catch (Exception e) { 43 log.error("parse exception", e); 44 } 45 } 46 47 public static void main(String[] args) { 48 // 定義線程池 49 ExecutorService executorService = Executors.newCachedThreadPool(); 50 // 定義信號量,信號量里面需要定義允許並發的數量 51 final Semaphore semaphore = new Semaphore(threadTotal); 52 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 53 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 54 // 放入請求操作 55 for (int i = 0; i < clientTotal; i++) { 56 final int count = i; 57 // 所有請求放入到線程池結果中 58 executorService.execute(() -> { 59 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 60 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 61 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 62 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 63 try { 64 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 65 semaphore.acquire(); 66 // 核心執行方法。 67 update(count); 68 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 69 semaphore.release(); 70 } catch (InterruptedException e) { 71 e.printStackTrace(); 72 } 73 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 74 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 75 // 執行countDown()方法計數器減一。 76 countDownLatch.countDown(); 77 }); 78 } 79 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 80 try { 81 // 調用await()方法當前進程進入等待狀態。 82 countDownLatch.await(); 83 } catch (InterruptedException e) { 84 e.printStackTrace(); 85 } 86 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 87 executorService.shutdown(); 88 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 89 } 90 91 }
6.3、集合Map的HashMap,線程不安全的。集合Map的Hashtable,是線程安全的。
集合Map的HashMap,線程不安全的,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.concurrent.CountDownLatch; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.Semaphore; 9 10 import com.bie.concurrency.annoations.NotThreadSafe; 11 12 import lombok.extern.slf4j.Slf4j; 13 14 /** 15 * 16 * 17 * @Title: HashMapExample.java 18 * @Package com.bie.concurrency.example.commonUnsafe 19 * @Description: TODO 20 * @author biehl 21 * @date 2020年1月9日 22 * @version V1.0 23 * 24 */ 25 @Slf4j 26 @NotThreadSafe // 由於每次結果不一致,所以是線程不安全的類。可以使用此程序進行並發測試。 27 public class HashMapExample { 28 29 public static int clientTotal = 5000;// 5000個請求,請求總數 30 31 public static int threadTotal = 200;// 允許同時並發執行的線程數目 32 33 private static Map<Integer, Integer> map = new HashMap<Integer, Integer>(); 34 35 private static void update(int count) { 36 try { 37 map.put(count, count); 38 } catch (Exception e) { 39 log.error("parse exception", e); 40 } 41 } 42 43 public static void main(String[] args) { 44 // 定義線程池 45 ExecutorService executorService = Executors.newCachedThreadPool(); 46 // 定義信號量,信號量里面需要定義允許並發的數量 47 final Semaphore semaphore = new Semaphore(threadTotal); 48 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 49 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 50 // 放入請求操作 51 for (int i = 0; i < clientTotal; i++) { 52 final int count = i; 53 // 所有請求放入到線程池結果中 54 executorService.execute(() -> { 55 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 56 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 57 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 58 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 59 try { 60 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 61 semaphore.acquire(); 62 // 核心執行方法。 63 update(count); 64 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 65 semaphore.release(); 66 } catch (InterruptedException e) { 67 e.printStackTrace(); 68 } 69 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 70 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 71 // 執行countDown()方法計數器減一。 72 countDownLatch.countDown(); 73 }); 74 } 75 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 76 try { 77 // 調用await()方法當前進程進入等待狀態。 78 countDownLatch.await(); 79 } catch (InterruptedException e) { 80 e.printStackTrace(); 81 } 82 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 83 executorService.shutdown(); 84 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 85 log.info("size:{}", map.size()); 86 87 } 88 }
集合Map的Hashtable,線程安全的,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.util.Hashtable; 4 import java.util.Map; 5 import java.util.concurrent.CountDownLatch; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.Semaphore; 9 10 import com.bie.concurrency.annoations.ThreadSafe; 11 12 import lombok.extern.slf4j.Slf4j; 13 14 /** 15 * 16 * 17 * @Title: HashMapExample.java 18 * @Package com.bie.concurrency.example.commonUnsafe 19 * @Description: TODO 20 * @author biehl 21 * @date 2020年1月9日 22 * @version V1.0 23 * 24 */ 25 @Slf4j 26 @ThreadSafe // 由於每次結果一致,所以是線程安全的類。可以使用此程序進行並發測試。 27 public class HashTableExample2 { 28 29 public static int clientTotal = 5000;// 5000個請求,請求總數 30 31 public static int threadTotal = 200;// 允許同時並發執行的線程數目 32 33 // Hashtable線程安全的 34 private static Map<Integer, Integer> map = new Hashtable<Integer, Integer>(); 35 36 private static void update(int count) { 37 try { 38 map.put(count, count); 39 } catch (Exception e) { 40 log.error("parse exception", e); 41 } 42 } 43 44 public static void main(String[] args) { 45 // 定義線程池 46 ExecutorService executorService = Executors.newCachedThreadPool(); 47 // 定義信號量,信號量里面需要定義允許並發的數量 48 final Semaphore semaphore = new Semaphore(threadTotal); 49 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 50 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 51 // 放入請求操作 52 for (int i = 0; i < clientTotal; i++) { 53 final int count = i; 54 // 所有請求放入到線程池結果中 55 executorService.execute(() -> { 56 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 57 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 58 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 59 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 60 try { 61 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 62 semaphore.acquire(); 63 // 核心執行方法。 64 update(count); 65 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 66 semaphore.release(); 67 } catch (InterruptedException e) { 68 e.printStackTrace(); 69 } 70 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 71 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 72 // 執行countDown()方法計數器減一。 73 countDownLatch.countDown(); 74 }); 75 } 76 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 77 try { 78 // 調用await()方法當前進程進入等待狀態。 79 countDownLatch.await(); 80 } catch (InterruptedException e) { 81 e.printStackTrace(); 82 } 83 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 84 executorService.shutdown(); 85 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 86 log.info("size:{}", map.size()); 87 88 } 89 }
6.4、集合List的ArrayList,線程不安全的,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.CountDownLatch; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.Semaphore; 9 10 import com.bie.concurrency.annoations.NotThreadSafe; 11 12 import lombok.extern.slf4j.Slf4j; 13 14 /** 15 * 16 * 17 * @Title: ArrayListExample1.java 18 * @Package com.bie.concurrency.example.commonUnsafe 19 * @Description: TODO 20 * @author biehl 21 * @date 2020年1月9日 22 * @version V1.0 23 * 24 */ 25 @Slf4j 26 @NotThreadSafe // 由於每次結果不一致,所以是線程不安全的類。可以使用此程序進行並發測試 27 public class ArrayListExample1 { 28 29 public static int clientTotal = 5000;// 5000個請求,請求總數 30 31 public static int threadTotal = 200;// 允許同時並發執行的線程數目 32 33 private static List<Integer> list = new ArrayList<>(); 34 35 private static void update(int count) { 36 try { 37 list.add(count); 38 } catch (Exception e) { 39 log.error("parse exception", e); 40 } 41 } 42 43 public static void main(String[] args) { 44 // 定義線程池 45 ExecutorService executorService = Executors.newCachedThreadPool(); 46 // 定義信號量,信號量里面需要定義允許並發的數量 47 final Semaphore semaphore = new Semaphore(threadTotal); 48 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 49 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 50 // 放入請求操作 51 for (int i = 0; i < clientTotal; i++) { 52 final int count = i; 53 // 所有請求放入到線程池結果中 54 executorService.execute(() -> { 55 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 56 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 57 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 58 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 59 try { 60 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 61 semaphore.acquire(); 62 // 核心執行方法。 63 update(count); 64 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 65 semaphore.release(); 66 } catch (InterruptedException e) { 67 e.printStackTrace(); 68 } 69 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 70 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 71 // 執行countDown()方法計數器減一。 72 countDownLatch.countDown(); 73 }); 74 } 75 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 76 try { 77 // 調用await()方法當前進程進入等待狀態。 78 countDownLatch.await(); 79 } catch (InterruptedException e) { 80 e.printStackTrace(); 81 } 82 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 83 executorService.shutdown(); 84 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 85 log.info("size:{}", list.size()); 86 87 } 88 }
6.5、集合Set的HashSet,線程不安全的,如下所示:
1 package com.bie.concurrency.example.commonUnsafe; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 import java.util.concurrent.CountDownLatch; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.Semaphore; 9 10 import com.bie.concurrency.annoations.NotThreadSafe; 11 12 import lombok.extern.slf4j.Slf4j; 13 14 /** 15 * 16 * 17 * @Title: HashSetExample1.java 18 * @Package com.bie.concurrency.example.commonUnsafe 19 * @Description: TODO 20 * @author biehl 21 * @date 2020年1月9日 22 * @version V1.0 23 * 24 */ 25 @Slf4j 26 @NotThreadSafe // 由於每次結果不一致,所以是線程不安全的類。可以使用此程序進行並發測試 27 public class HashSetExample1 { 28 29 public static int clientTotal = 5000;// 5000個請求,請求總數 30 31 public static int threadTotal = 200;// 允許同時並發執行的線程數目 32 33 private static Set<Integer> set = new HashSet<Integer>(); 34 35 private static void update(int count) { 36 try { 37 set.add(count); 38 } catch (Exception e) { 39 log.error("parse exception", e); 40 } 41 } 42 43 public static void main(String[] args) { 44 // 定義線程池 45 ExecutorService executorService = Executors.newCachedThreadPool(); 46 // 定義信號量,信號量里面需要定義允許並發的數量 47 final Semaphore semaphore = new Semaphore(threadTotal); 48 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 49 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 50 // 放入請求操作 51 for (int i = 0; i < clientTotal; i++) { 52 final int count = i; 53 // 所有請求放入到線程池結果中 54 executorService.execute(() -> { 55 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 56 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 57 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 58 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 59 try { 60 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 61 semaphore.acquire(); 62 // 核心執行方法。 63 update(count); 64 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 65 semaphore.release(); 66 } catch (InterruptedException e) { 67 e.printStackTrace(); 68 } 69 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 70 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 71 // 執行countDown()方法計數器減一。 72 countDownLatch.countDown(); 73 }); 74 } 75 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 76 try { 77 // 調用await()方法當前進程進入等待狀態。 78 countDownLatch.await(); 79 } catch (InterruptedException e) { 80 e.printStackTrace(); 81 } 82 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 83 executorService.shutdown(); 84 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 85 log.info("size:{}", set.size()); 86 87 } 88 }
7、線程不安全的類,比如Arraylist、HashSet、HashMap,這些容器都是非線程安全的,如果有多個線程並發訪問這些容器的時候,就會出現線程安全的問題。因此,編寫程序的時候,必須手動在任何訪問到這些容器的地方進行同步處理,這樣導致了在使用這些容器的時候非常不方便。因此java提高了同步容器,方便進行使用相關的容器。同步容器中的方法主要采用了synchronized方法進行同步的,很顯然,這會影響到執行的性能,同步容器很難做到線程的安全,同步容器性能不是很好。並發容器,可以很好的實現線程的安全,而且性能比同步容器好。
同步容器主要包含兩大類。
1)、第一大類。ArrayList ==》 Vector(底層實現是數組,進行了同步操作,使用synchronized修飾的方法,多線程環境下可以使用,線程安全性會更好一些,不是線程安全的哦,只是會更好一些)、Stack(繼承於Vector,同步容器,使用synchronized修飾的方法,Stack就是數據結構里面的棧,效果是先進后出)。HashMap ==》 HashTable(key,value不能為null,HashTable實現了Map接口,使用synchronized修飾的方法)。
2)、第二大類,Collections.synchronizedXXX(List、Set、Map)。Collections是工具提供的類,大量的工具類。對集合或者容器進行排序、查詢等等操作。
7.1、Collections是工具提供的類,大量的工具類。對集合或者容器進行排序、查詢等等操作。Collections.synchronizedList(Lists.newArrayList());這個是List集合的,其他Set集合、Map集合類似,如下所示:
1 package com.bie.concurrency.example.syncContainer; 2 3 import java.util.Collections; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7 import java.util.Set; 8 import java.util.concurrent.CountDownLatch; 9 import java.util.concurrent.ExecutorService; 10 import java.util.concurrent.Executors; 11 import java.util.concurrent.Semaphore; 12 13 import com.bie.concurrency.annoations.ThreadSafe; 14 import com.google.common.collect.Lists; 15 import com.google.common.collect.Sets; 16 17 import lombok.extern.slf4j.Slf4j; 18 19 /** 20 * 21 * 22 * @Title: CollectionsExample1.java 23 * @Package com.bie.concurrency.example.syncContainer 24 * @Description: TODO 25 * @author biehl 26 * @date 2020年1月13日 27 * @version V1.0 28 * 29 */ 30 @Slf4j 31 @ThreadSafe // 由於每次結果一致,所以是線程安全的類。可以使用此程序進行並發測試。 32 public class CollectionsExample1 { 33 34 public static int clientTotal = 5000;// 5000個請求,請求總數 35 36 public static int threadTotal = 200;// 允許同時並發執行的線程數目 37 38 // Collections是工具提供的類,大量的工具類。 39 private static List<Integer> list = Collections.synchronizedList(Lists.newArrayList()); 40 // private static Map<Integer, Integer> map = Collections.synchronizedMap(new HashMap<>()); 41 // private static Set<Integer> set = Collections.synchronizedSet(Sets.newHashSet()); 42 43 private static void update(int count) { 44 list.add(count); 45 } 46 47 public static void main(String[] args) { 48 // 定義線程池 49 ExecutorService executorService = Executors.newCachedThreadPool(); 50 // 定義信號量,信號量里面需要定義允許並發的數量 51 final Semaphore semaphore = new Semaphore(threadTotal); 52 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 53 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 54 // 放入請求操作 55 for (int i = 0; i < clientTotal; i++) { 56 final int count = i; 57 // 所有請求放入到線程池結果中 58 executorService.execute(() -> { 59 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 60 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 61 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 62 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 63 try { 64 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 65 semaphore.acquire(); 66 // 核心執行方法。 67 update(count); 68 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 69 semaphore.release(); 70 } catch (InterruptedException e) { 71 e.printStackTrace(); 72 } 73 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 74 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 75 // 執行countDown()方法計數器減一。 76 countDownLatch.countDown(); 77 }); 78 } 79 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 80 try { 81 // 調用await()方法當前進程進入等待狀態。 82 countDownLatch.await(); 83 } catch (InterruptedException e) { 84 e.printStackTrace(); 85 } 86 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 87 executorService.shutdown(); 88 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 89 log.info("size:{}", list.size()); 90 91 } 92 }
7.2、HashMap -> HashTable(key,value不能為null,HashTable實現了Map接口,使用synchronized修飾的方法)。
1 package com.bie.concurrency.example.syncContainer; 2 3 import java.util.Hashtable; 4 import java.util.Map; 5 import java.util.concurrent.CountDownLatch; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.Semaphore; 9 10 import com.bie.concurrency.annoations.ThreadSafe; 11 12 import lombok.extern.slf4j.Slf4j; 13 14 /** 15 * 16 * 17 * @Title: HashTableExample.java 18 * @Package com.bie.concurrency.example.syncContainer 19 * @Description: TODO 20 * @author biehl 21 * @date 2020年1月13日 22 * @version V1.0 23 * 24 */ 25 @Slf4j 26 @ThreadSafe // 由於每次結果一致,所以是線程安全的類。可以使用此程序進行並發測試。 27 public class HashTableExample { 28 29 public static int clientTotal = 5000;// 5000個請求,請求總數 30 31 public static int threadTotal = 200;// 允許同時並發執行的線程數目 32 33 // Hashtable線程安全的。 34 private static Map<Integer, Integer> map = new Hashtable<>(); 35 36 private static void update(int count) { 37 map.put(count, count); 38 } 39 40 public static void main(String[] args) { 41 // 定義線程池 42 ExecutorService executorService = Executors.newCachedThreadPool(); 43 // 定義信號量,信號量里面需要定義允許並發的數量 44 final Semaphore semaphore = new Semaphore(threadTotal); 45 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 46 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 47 // 放入請求操作 48 for (int i = 0; i < clientTotal; i++) { 49 final int count = i; 50 // 所有請求放入到線程池結果中 51 executorService.execute(() -> { 52 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 53 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 54 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 55 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 56 try { 57 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 58 semaphore.acquire(); 59 // 核心執行方法。 60 update(count); 61 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 62 semaphore.release(); 63 } catch (InterruptedException e) { 64 e.printStackTrace(); 65 } 66 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 67 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 68 // 執行countDown()方法計數器減一。 69 countDownLatch.countDown(); 70 }); 71 } 72 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 73 try { 74 // 調用await()方法當前進程進入等待狀態。 75 countDownLatch.await(); 76 } catch (InterruptedException e) { 77 e.printStackTrace(); 78 } 79 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 80 executorService.shutdown(); 81 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 82 log.info("size:{}", map.size()); 83 84 } 85 }
7.3、Vector(底層實現是數組,進行了同步操作,使用synchronized修飾的方法,多線程環境下可以使用,線程安全性會更好一些,不是線程安全的哦,只是會更好一些)、Stack(繼承於Vector,同步容器,使用synchronized修飾的方法,Stack就是數據結構里面的棧,效果是先進后出)。
1 package com.bie.concurrency.example.syncContainer; 2 3 import java.util.Collections; 4 import java.util.List; 5 import java.util.Vector; 6 import java.util.concurrent.CountDownLatch; 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Executors; 9 import java.util.concurrent.Semaphore; 10 11 import com.bie.concurrency.annoations.ThreadSafe; 12 import com.google.common.collect.Lists; 13 14 import lombok.extern.slf4j.Slf4j; 15 16 /** 17 * 18 * 19 * @Title: VectorExample1.java 20 * @Package com.bie.concurrency.example.syncContainer 21 * @Description: TODO 22 * @author biehl 23 * @date 2020年1月13日 24 * @version V1.0 25 * 26 */ 27 @Slf4j 28 @ThreadSafe // 由於每次結果一致,所以是線程安全的類。可以使用此程序進行並發測試。 29 public class VectorExample2 { 30 31 public static int clientTotal = 5000;// 5000個請求,請求總數 32 33 public static int threadTotal = 200;// 允許同時並發執行的線程數目 34 35 // Vector使用synchronized修飾的,但是不是絕對線程安全的,也會出現線程不安全的情況,可能是出現數據越界的情況。 36 // 注意,不是任意時候都是線程安全的哦 37 private static List<Integer> list = new Vector<>(); 38 39 private static void update(int count) { 40 list.add(count); 41 } 42 43 public static void main(String[] args) { 44 // 定義線程池 45 ExecutorService executorService = Executors.newCachedThreadPool(); 46 // 定義信號量,信號量里面需要定義允許並發的數量 47 final Semaphore semaphore = new Semaphore(threadTotal); 48 // 定義計數器閉鎖,希望所有請求完以后統計計數結果,將計數結果放入 49 final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); 50 // 放入請求操作 51 for (int i = 0; i < clientTotal; i++) { 52 final int count = i; 53 // 所有請求放入到線程池結果中 54 executorService.execute(() -> { 55 // 在線程池執行的時候引入了信號量,信號量每次做acquire()操作的時候就是判斷當前進程是否允許被執行。 56 // 如果達到了一定並發數的時候,add方法可能會臨時被阻塞掉。當acquire()可以返回值的時候,add方法可以被執行。 57 // add方法執行完畢以后,釋放當前進程,此時信號量就已經引入完畢了。 58 // 在引入信號量的基礎上引入閉鎖機制。countDownLatch 59 try { 60 // 執行核心執行方法之前引入信號量,信號量每次允許執行之前需要調用方法acquire()。 61 semaphore.acquire(); 62 // 核心執行方法。 63 update(count); 64 // 核心執行方法執行完成以后,需要釋放當前進程,釋放信號量。 65 semaphore.release(); 66 } catch (InterruptedException e) { 67 e.printStackTrace(); 68 } 69 // try-catch是一次執行系統的操作,執行完畢以后調用一下閉鎖。 70 // 每次執行完畢以后countDownLatch里面對應的計算值減一。 71 // 執行countDown()方法計數器減一。 72 countDownLatch.countDown(); 73 }); 74 } 75 // 這個方法可以保證之前的countDownLatch必須減為0,減為0的前提就是所有的進程必須執行完畢。 76 try { 77 // 調用await()方法當前進程進入等待狀態。 78 countDownLatch.await(); 79 } catch (InterruptedException e) { 80 e.printStackTrace(); 81 } 82 // 通常,線程池執行完畢以后,線程池不再使用,記得關閉線程池 83 executorService.shutdown(); 84 // 如果我們希望在所有線程執行完畢以后打印當前計數的值。只需要log.info之前執行上一步即可countDownLatch.await();。 85 log.info("size:{}", list.size()); 86 87 } 88 }
安全共享對象策略。
1)、線程限制,一個被線程限制的對象,由線程獨占,並且只能被占有它的線程修改。
2)、共享只讀,一個共享只讀的對象,在沒有額外同步的情況下,可以被多個線程並發訪問,但是任何線程都不能修改它。
3)、線程安全對象,一個線程安全的對象或者容器,在內部通過同步機制來保證線程安全,所以其他線程無需額外的同步就可以通過公共接口隨意訪問它。
4)、被守護對象,被守護對象只能通過獲取特定的鎖來訪問。
作者:別先生
博客園:https://www.cnblogs.com/biehongli/
如果您想及時得到個人撰寫文章以及著作的消息推送,可以掃描上方二維碼,關注個人公眾號哦。