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)、被守护对象,被守护对象只能通过获取特定的锁来访问。
 

作者:别先生

博客园:IT虾米网

如果您想及时得到个人撰写文章以及著作的消息推送,可以扫描上方二维码,关注个人公众号哦。


评论关闭
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

Java并发编程与高并发之安全发布对象