单例模式

应用:单例模式

饿汉式单例(线程安全)

特点:线程安全,相对于懒汉式单例,其开销相比高一点

  1. //饿汉式单例
  2. class Person{
  3. //1.私有化类的构造器,使其在外部不能new
  4. private Person(){}
  5. //2.内部创建类的对象,要求此对象也必须声明为静态的—随着类的创建而创建,从创建到销毁只有一份
  6. private static Person instance = new Person();
  7. //3.提供公共的静态的方法,返回类的对象
  8. public static Person getInstance(){
  9. return instance;
  10. }
  11. }

懒汉式单例(线程不安全版本)

特点:使用的时候才去new;线程不安全,需要改造

  1. class Person {
  2. //1.私有化类的构造器
  3. private Person(){}
  4. //2.声明当前类对象(由于第3步的getInstance也是static,故此对象也必须声明为static)
  5. private static Person instance = null;
  6. //3.声明public、static的返回当前类对象的方法
  7. public static Person getInstance(){
  8. if(instance == null){
  9. instance = new Person();
  10. }
  11. return instance;
  12. }
  13. }

懒汉式单例(线程非绝对安全版本)

  1. class AppServiceProcessor{
  2. private AppServiceProcessor(){}
  3. private static AppServiceProcessor instance = null;
  4. public static AppServiceProcessor getInstance(){
  5. if(instance == null){
  6. synchronized (AppServiceProcessor.class) {
  7. if(instance == null){
  8. instance = new AppServiceProcessor();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

懒汉式单例(线程安全版本)=>加入volatile关键字

  1. class AppServiceProcessor{
  2. private AppServiceProcessor(){}
  3. private static volatile AppServiceProcessor instance = null;
  4. public static AppServiceProcessor getInstance(){
  5. if(instance == null){
  6. synchronized (AppServiceProcessor.class) {
  7. if(instance == null){
  8. instance = new AppServiceProcessor();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

生产者/消费者问题

使用synchronized+lockObj.wait()+lockObj.notify()来实现

  1. public class ProducerAndConsumer {
  2. static int count = 10;
  3. static Lock lock = new ReentrantLock();
  4. static Condition condition = lock.newCondition();
  5. public static void main(String[] args) {
  6. //消费者线程:小于0个就停止消费,否则一直消费
  7. Thread tdConsumer = new Thread() {
  8. @Override
  9. public void run() {
  10. super.run();
  11. try {
  12. lock.lock();
  13. while (true) {
  14. if (count <= 0) {
  15. condition.signal();
  16. condition.await();
  17. } else {
  18. count--;
  19. System.out.println(" 消费一个,剩余" + count);
  20. }
  21. }
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. } finally {
  25. lock.unlock();
  26. }
  27. }
  28. };
  29. tdConsumer.start();
  30. //生产着线程:大于10个就一直生产
  31. Thread tdProducer = new Thread() {
  32. @Override
  33. public void run() {
  34. super.run();
  35. try {
  36. lock.lock();
  37. while (true) {
  38. if (count < 10) {
  39. count++;
  40. System.out.println("生产一个,剩余" + count);
  41. } else {
  42. condition.signal();
  43. condition.await();
  44. }
  45. }
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. } finally {
  49. lock.unlock();
  50. }
  51. }
  52. };
  53. tdProducer.start();
  54. }
  55. }
  56. //效果
  57. 消费一个,剩余9
  58. 消费一个,剩余8
  59. 消费一个,剩余7
  60. 消费一个,剩余6
  61. 消费一个,剩余5
  62. 消费一个,剩余4
  63. 消费一个,剩余3
  64. 消费一个,剩余2
  65. 消费一个,剩余1
  66. 消费一个,剩余0
  67. 生产一个,剩余1
  68. 生产一个,剩余2
  69. 生产一个,剩余3
  70. 生产一个,剩余4
  71. 生产一个,剩余5
  72. 生产一个,剩余6
  73. 生产一个,剩余7
  74. 生产一个,剩余8
  75. 生产一个,剩余9
  76. 生产一个,剩余10

使用lock+ lockobj.await+ lockobj.signal来实现
需求:消费者线程小于0个就停止消费,阻塞并唤醒生产者线程生产,否则一直消费;生产者线程大于10个就一直生产,否则阻塞并唤醒消费者线程进行消费。

  1. class Ecoco10ApplicationTests {
  2. private Object object = new Object();
  3. private int count = 0;
  4. @Test
  5. void TestThread() throws InterruptedException {
  6. //生产者线程
  7. Thread threadProducer = new Thread() {
  8. @SneakyThrows
  9. @Override
  10. public void run() {
  11. while (true) {
  12. synchronized (object) {
  13. Thread.sleep(3000);
  14. if (count == 0) {
  15. count++;
  16. System.out.println("线程1进行了生产" + count);
  17. object.notify();//已经生产完了,通知被阻塞的消费线程
  18. }
  19. object.wait();//已经生产完了,阻塞当前线程并释放同步监视器
  20. }
  21. }
  22. }
  23. };
  24. //消费者线程
  25. Thread threadConsumer = new Thread() {
  26. @SneakyThrows
  27. @Override
  28. public void run() {
  29. while (true) {
  30. synchronized (object) {
  31. Thread.sleep(3000);
  32. if (count > 0) {
  33. count--;
  34. System.out.println("线程2进行了消费" + count);
  35. object.notify();//已经消费完了,通知被阻塞的生产线程
  36. }
  37. object.wait();//已经消费完了,阻塞当前线程并释放同步监视器
  38. }
  39. }
  40. }
  41. };
  42. threadProducer.start();
  43. threadConsumer.start();
  44. Thread.sleep(50000);
  45. }
  46. }

使用阻塞队列来实现

  1. //实现了生产者-消费者模式的类
  2. class MyProductorComsumer {
  3. //默认开启,进行生产消费
  4. //这里用到了volatile是为了保持数据的可见性,也就是当FLAG修改时,要马上通知其它线程进行修改
  5. private volatile boolean IS_PROD = true;
  6. //使用原子包装类,而不用number++
  7. private AtomicInteger atomicInteger = new AtomicInteger();
  8. //这里不能为了满足条件,而实例化一个具体的SynchronousBlockingQueue
  9. BlockingQueue<String> blockingQueue = null;
  10. //而应该采用依赖注入里面的,构造注入方法传入
  11. public MyResource(BlockingQueue<String> blockingQueue) {
  12. this.blockingQueue = blockingQueue;
  13. //查询出传入的class是什么
  14. System.out.println(blockingQueue.getClass().getName());
  15. }
  16. /**
  17. * 生产
  18. *
  19. * @throws Exception
  20. */
  21. public void myProd() throws Exception {
  22. String data = null;
  23. boolean retValue;
  24. //多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
  25. //当FLAG为true的时候,开始生产
  26. while (IS_PROD) {
  27. data = atomicInteger.incrementAndGet() + "";
  28. //2秒生产一个数据
  29. retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
  30. if (retValue) {
  31. System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data + "成功");
  32. } else {
  33. System.out.println(Thread.currentThread().getName() + "\t 插入队列:" + data + "失败");
  34. }
  35. try {
  36. TimeUnit.SECONDS.sleep(1);
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. System.out.println(Thread.currentThread().getName() + "\t 停止生产,表示FLAG=false,生产介绍");
  42. }
  43. /**
  44. * 消费
  45. *
  46. * @throws Exception
  47. */
  48. public void myConsumer() throws Exception {
  49. String retValue;
  50. //多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
  51. //当FLAG为true的时候,开始生产
  52. while (IS_PROD) {
  53. // 2秒存入1个data
  54. retValue = blockingQueue.poll(2L, TimeUnit.SECONDS);
  55. if (retValue != null && retValue != "") {
  56. System.out.println(Thread.currentThread().getName() + "\t 消费队列:" + retValue + "成功");
  57. } else {
  58. IS_PROD = false;
  59. System.out.println(Thread.currentThread().getName() + "\t 消费失败,队列中已为空,退出");
  60. //退出消费队列
  61. return;
  62. }
  63. }
  64. }
  65. /**
  66. * 停止生产的判断
  67. */
  68. public void stop() {
  69. this.IS_PROD = false;
  70. }
  71. }
  72. //使用处
  73. package com.fly.ecoco10;
  74. import java.util.concurrent.ArrayBlockingQueue;
  75. import java.util.concurrent.BlockingQueue;
  76. import java.util.concurrent.TimeUnit;
  77. import java.util.concurrent.atomic.AtomicInteger;
  78. public class BlockingQueueDemo {
  79. public static void main(String[] args) {
  80. //传入具体的实现类, ArrayBlockingQueue
  81. MyProductorComsumer myResource = new MyProductorComsumer(new ArrayBlockingQueue<>(10));
  82. new Thread(() -> {
  83. System.out.println(Thread.currentThread().getName() + "\t 生产线程启动");
  84. System.out.println("");
  85. System.out.println("");
  86. try {
  87. myResource.myProd();
  88. System.out.println("");
  89. System.out.println("");
  90. } catch (Exception e) {
  91. e.printStackTrace();
  92. }
  93. }, "prod").start();
  94. new Thread(() -> {
  95. System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
  96. try {
  97. myResource.myConsumer();
  98. } catch (Exception e) {
  99. e.printStackTrace();
  100. }
  101. }, "consumer").start();
  102. //5秒后,停止生产和消费
  103. try {
  104. TimeUnit.SECONDS.sleep(5);
  105. } catch (InterruptedException e) {
  106. e.printStackTrace();
  107. }
  108. System.out.println("");
  109. System.out.println("5秒中后,生产和消费线程停止,线程结束");
  110. myResource.stop();
  111. }
  112. }
  113. //效果
  114. java.util.concurrent.ArrayBlockingQueue
  115. prod 生产线程启动
  116. prod 插入队列:1成功
  117. consumer 消费线程启动
  118. consumer 消费队列:1成功
  119. prod 插入队列:2成功
  120. consumer 消费队列:2成功
  121. prod 插入队列:3成功
  122. consumer 消费队列:3成功
  123. prod 插入队列:4成功
  124. consumer 消费队列:4成功
  125. prod 插入队列:5成功
  126. consumer 消费队列:5成功
  127. prod 插入队列:6成功
  128. consumer 消费队列:6成功
  129. 5秒中后,生产和消费线程停止,线程结束
  130. prod 停止生产,表示FLAG=false,生产介绍
  131. consumer 消费失败,队列中已为空,退出

集合类的线程安全问题

文档参考

ArrayList

线程不安全写法

  1. public class ListDemo {
  2. static ArrayList<Integer> arrayList = new ArrayList<>();
  3. public static void main(String[] args) {
  4. for (int i = 0; i < 10000; i++) {
  5. new Thread() {
  6. @Override
  7. public void run() {
  8. super.run();
  9. arrayList.add(100);
  10. System.out.println(arrayList);
  11. }
  12. }.start();
  13. }
  14. }
  15. }
  16. //会出现的错误:
  17. Exception in thread "Thread-8605" java.util.ConcurrentModificationException
  18. at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
  19. at java.util.ArrayList$Itr.next(ArrayList.java:861)
  20. at java.util.AbstractCollection.toString(AbstractCollection.java:461)
  21. at java.lang.String.valueOf(String.java:2994)
  22. at java.io.PrintStream.println(PrintStream.java:821)
  23. at ListDemo$1.run(ListDemo.java:17)

解决方案一:Vector
使用线程安全的Vector类,该方法在方法上加了锁。缺点是:方法加了锁,会导致并发性能下降。
image.png

  1. public class ListDemo {
  2. //static ArrayList<Integer> arrayList = new ArrayList<>();
  3. static Vector<Integer> arrayList = new Vector<>();
  4. public static void main(String[] args) {
  5. for (int i = 0; i < 10000; i++) {
  6. new Thread() {
  7. @Override
  8. public void run() {
  9. super.run();
  10. arrayList.add(100);
  11. System.out.println(arrayList);
  12. }
  13. }.start();
  14. }
  15. }
  16. }
  17. //正常输出每次的值

解决方案二:使用Collections.synchronized()

  1. public class ListDemo {
  2. //static ArrayList<Integer> arrayList = new ArrayList<>();
  3. //static Vector<Integer> arrayList = new Vector<>();
  4. static List<Integer> arrayList = Collections.synchronizedList(new ArrayList<>());
  5. public static void main(String[] args) {
  6. for (int i = 0; i < 10000; i++) {
  7. new Thread() {
  8. @Override
  9. public void run() {
  10. super.run();
  11. arrayList.add(100);
  12. System.out.println(arrayList);
  13. }
  14. }.start();
  15. }
  16. }
  17. }
  18. //正常输出每次的值

解决方案三:使用JUC.CopyOnWriteArrayList类
CopyOnWriteArrayList:写时复制,主要是一种读写分离的思想。CopyOnWrite容器即写时复制的容器,往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是先将Object[]进行copy,复制出一个新的容器object[] newElements,然后新的容器Object[] newElements里添加原始,添加元素完后,在将原容器的引用指向新的容器 setArray(newElements);这样做的好处是可以对copyOnWrite容器进行并发的读 ,而不需要加锁,因为当前容器不需要添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写使用不同的容器。

  1. //该类底层的添加方法
  2. public boolean add(E e) {
  3. //首先加锁
  4. final ReentrantLock lock = this.lock;
  5. lock.lock();
  6. try {
  7. Object[] elements = getArray();
  8. int len = elements.length;
  9. //在末尾扩容一个单位
  10. Object[] newElements = Arrays.copyOf(elements, len + 1);
  11. newElements[len] = e;
  12. setArray(newElements);
  13. return true;
  14. } finally {
  15. lock.unlock();
  16. }
  17. }

改造后的方法

  1. public class ListDemo {
  2. //static ArrayList<Integer> arrayList = new ArrayList<>();
  3. //static Vector<Integer> arrayList = new Vector<>();
  4. //static List<Integer> arrayList = Collections.synchronizedList(new ArrayList<>());
  5. static CopyOnWriteArrayList<Integer> arrayList=new CopyOnWriteArrayList();
  6. public static void main(String[] args) {
  7. for (int i = 0; i < 10000; i++) {
  8. new Thread() {
  9. @Override
  10. public void run() {
  11. super.run();
  12. arrayList.add(100);
  13. System.out.println(arrayList);
  14. }
  15. }.start();
  16. }
  17. }
  18. }

HashSet

概述:HashSet在多线程情况下是线程不安全的。
解决方案:使用CopyOnWriteArraySet进行实例化,该类底层还是使用CopyOnWriteArrayList类。

  1. public class ListDemo {
  2. static CopyOnWriteArraySet arraySet=new CopyOnWriteArraySet();
  3. public static void main(String[] args) {
  4. for (int i = 0; i < 10000; i++) {
  5. new Thread() {
  6. @Override
  7. public void run() {
  8. super.run();
  9. for (int i = 0; i < 30; i++) {
  10. new Thread(() -> {
  11. arraySet.add(100);
  12. }, String.valueOf(i)).start();
  13. }
  14. }
  15. }.start();
  16. }
  17. }
  18. }

扩展:HashSet的底层结构就是HashMap,往HashSet添加数据,key即传入的值,value即object。

  1. public class HashSet<E>{
  2. private static final Object PRESENT = new Object();
  3. public HashSet() {
  4. map = new HashMap<>();
  5. }
  6. //我们能发现但我们调用add的时候,存储一个值进入map中,只是作为key进行存储,而value存储的是一个Object类型的常量,也就是说HashSet只关心key,而不关心value。value也就是固定的static final new object值
  7. public boolean add(E e) {
  8. return map.put(e, PRESENT)==null;
  9. }
  10. }

HashMap

概述:HashMap在多线程环境下,也是不安全的。

  1. public class ListDemo {
  2. static Map<String, String> map = new HashMap<>();
  3. public static void main(String[] args) {
  4. for (int i = 0; i < 10000; i++) {
  5. new Thread() {
  6. @Override
  7. public void run() {
  8. super.run();
  9. for (int i = 0; i < 30; i++) {
  10. new Thread(() -> {
  11. map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
  12. System.out.println(map);
  13. }, String.valueOf(i)).start();
  14. }
  15. }
  16. }.start();
  17. }
  18. }
  19. }

解决方案
1、使用Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

  1. public class ListDemo {
  2. static Map<String, String> map = null;
  3. public static void main(String[] args) {
  4. //实例化线程安全版本的Map<Key,Value>
  5. map= Collections.synchronizedMap(new HashMap<>());
  6. for (int i = 0; i < 10000; i++) {
  7. new Thread() {
  8. @Override
  9. public void run() {
  10. super.run();
  11. for (int i = 0; i < 30; i++) {
  12. new Thread(() -> {
  13. map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
  14. System.out.println(map);
  15. }, String.valueOf(i)).start();
  16. }
  17. }
  18. }.start();
  19. }
  20. }
  21. }

2、使用 ConcurrentHashMap

  1. public class ListDemo {
  2. static ConcurrentHashMap map=new ConcurrentHashMap();
  3. public static void main(String[] args) {
  4. for (int i = 0; i < 10000; i++) {
  5. new Thread() {
  6. @Override
  7. public void run() {
  8. super.run();
  9. for (int i = 0; i < 30; i++) {
  10. new Thread(() -> {
  11. map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
  12. System.out.println(map);
  13. }, String.valueOf(i)).start();
  14. }
  15. }
  16. }.start();
  17. }
  18. }
  19. }