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