Java並發編程與高並發之線程安全策略


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/

如果您想及時得到個人撰寫文章以及著作的消息推送,可以掃描上方二維碼,關注個人公眾號哦。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM