• 创建型
    • 简单工厂模式
    • 工厂模式
    • 抽象工厂模式
    • 单例模式
      • 为什么枚举能够实现单例 ?
    • 建造者模式
    • 原型模式
  • 结构型
    • 代理模式
    • 适配器模式
    • 桥梁模式
    • 装饰模式
    • 门面模式
    • 组合模式
    • 享元模式
  • 行为型
    • 策略模式
    • 观察者模式
    • 责任链模式
    • 模板方法模式
    • 状态模式

      单例模式

      保证一个类仅有一个实例,并提供一个访问它的全局访问点
线程安全 并发性能好 可以延迟加载 序列化/反序列化安全 能抵御反射攻击
饿汉式 Y Y
懒汉式-不加锁 Y Y
懒汉式-加锁 Y Y
双重检查 Double Check Y Y Y
静态内部类 Y Y Y
枚举 Y Y Y

懒汉式:第一次使用的时候才进行加载

  1. // 非线程安全
  2. public class Singleton {
  3. private Singleton(){}
  4. private static Singleton singleton;
  5. public static Singleton getSingleton(){
  6. if (singleton == null){
  7. singleton = new Singleton();
  8. }
  9. return singleton;
  10. }
  11. }
  12. // 加锁线程安全,但是每次获取都会加锁判断
  13. public class Singleton {
  14. private Singleton(){}
  15. private static Singleton singleton;
  16. public static synchronized Singleton getSingleton(){
  17. if (singleton == null){
  18. singleton = new Singleton();
  19. }
  20. return singleton;
  21. }
  22. }

饿汉式:

  • 在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。
  • 通过类加载机制保证单例,但是如果代码中有其它方式导致类加载(反射、反序列化),就不满足单例

    1. public class Singleton {
    2. public static Singleton singleton = new Singleton();
    3. private Singleton(){}
    4. public static getSingleton() {
    5. return singleton;
    6. }
    7. }

    双重校验(double check)

  1. 第一次校验 singleton 是否为空,是为了提高代码执行效率。单例模式只创建一次,后面调用就不用加锁直接返回已创建的实例。
  2. 第二次校验 singleton 是否为空,是防止二次创建实例。线程 A 、线程 B 同时进入第一个判断之后,线程 B 拿到锁创建了实例,然后线程 A 拿到锁之后如果不加以判断,就会再次创建实例。
  3. singleton 用 volatile 修饰是为了防止 JVM 指令重排序。singleton = new Singleton() 分为以下 3 步,步骤 3 可能在步骤 2 之前执行,此时另外的线程发现 singleton 不为 null,直接跳过第一个判断,返回未初始化完全的对象就会出问题。

    1. 分配内存空间
    2. 初始化对象
    3. 内存空间赋值给对应的引用

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

      静态内部类

  • 满足延迟加载:Singleton 类被装载了,instance 不一定被初始化,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类。对于类的初始化,虚拟机规范则严格规定了有且只有四种情况必须立即对类进行初始化,遇到 new、getStatic、putStatic 或 invokeStatic 这 4 条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令最常见的 java 代码场景是:
    1)使用 new 关键字实例化对象
    2)读取一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)
    3)设置一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)
    4)调用一个类的静态方法
  • 执行类的初始化期间,JVM 会获取一个锁,同步多个线程对同一个类的初始化,所以可以保证线程安全
  • 这种方式只适用于静态域的情况,静态内部类只能访问外部类的静态成员变量。

    1. public class Singleton {
    2. private Singleton (){}
    3. private static class SingletonHolder {
    4. private static final Singleton INSTANCE = new Singleton();
    5. }
    6. public static final Singleton getSingleton(){
    7. return SingletonHolder.INSTANCE;
    8. }
    9. }

    枚举

    1. public enum Singleton {
    2. INSTANCE;
    3. public void doSomeThing() {
    4. }
    5. }

生产者与消费者

image.png

Synchronize 关键字实现

  1. public class TestA {
  2. // 定义共享资源区
  3. class Resource{
  4. private int currCount = 0;
  5. private int maxCount = 3;
  6. private Object object = new Object();
  7. // 存数据之前先拿到对象锁
  8. public void put() throws Exception{
  9. synchronized (object){
  10. while (currCount >= maxCount){
  11. try {
  12. object.wait();
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. currCount++;
  18. System.out.println(Thread.currentThread().getName() + " put:"+currCount);
  19. object.notify();
  20. }
  21. }
  22. public void take() throws Exception{
  23. synchronized (object){
  24. while (currCount == 0){
  25. try {
  26. object.wait();
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. currCount--;
  32. System.out.println(Thread.currentThread().getName() + " take:"+currCount);
  33. object.notify();
  34. }
  35. }
  36. }
  37. // 生产者,传入资源实例 存数据
  38. class Producer implements Runnable{
  39. private Resource resource;
  40. public Producer(Resource resource){
  41. this.resource = resource;
  42. }
  43. @Override
  44. public void run() {
  45. while (true){
  46. try {
  47. sleep(1000);
  48. resource.put();
  49. } catch (Exception e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }
  54. }
  55. // 消费者,传入资源实例 取数据
  56. class Consumer implements Runnable{
  57. private Resource resource;
  58. public Consumer(Resource resource){
  59. this.resource = resource;
  60. }
  61. @Override
  62. public void run() {
  63. while (true){
  64. try {
  65. sleep(1000);
  66. resource.take();
  67. } catch (Exception e) {
  68. e.printStackTrace();
  69. }
  70. }
  71. }
  72. }
  73. public static void main(String[] args) {
  74. TestA testA = new TestA();
  75. Resource resource = testA.new Resource();
  76. // 定义 3 个生产者, 1 个消费者
  77. new Thread(testA.new Producer(resource)).start();
  78. new Thread(testA.new Producer(resource)).start();
  79. new Thread(testA.new Producer(resource)).start();
  80. new Thread(testA.new Consumer(resource)).start();
  81. }
  82. }

Lock、Condition

与前面 synchronize 实现相比,主要区别就是 lock 需要在 finally 代码块中主动释放锁。

  1. public class TestB {
  2. // 定义共享资源区
  3. class Resource{
  4. private int currCount = 0;
  5. private int maxCount = 3;
  6. private Lock lock = new ReentrantLock();
  7. private Condition condition = lock.newCondition();
  8. public void put() throws Exception{
  9. lock.lock();
  10. try {
  11. while (currCount >= maxCount){
  12. condition.await();
  13. }
  14. currCount++;
  15. System.out.println(Thread.currentThread().getName() + " put:"+currCount);
  16. condition.signal();
  17. }catch (Exception e){
  18. e.printStackTrace();
  19. }finally {
  20. lock.unlock();
  21. }
  22. }
  23. public void take() throws Exception{
  24. lock.lock();
  25. try {
  26. while (currCount == 0){
  27. condition.await();
  28. }
  29. currCount--;
  30. System.out.println(Thread.currentThread().getName() + " take:"+currCount);
  31. condition.signal();
  32. }catch (Exception e){
  33. e.printStackTrace();
  34. }finally {
  35. lock.unlock();
  36. }
  37. }
  38. }
  39. ...
  40. 定义生产者、消费者、main 方法与上面一样,不再赘述。
  41. }

BlockingQueue

阻塞队列无需额外的关键字或者方法进行控制,它的底层实现逻辑就是 Lock 方法实现,当数据不满足取或者存的条件时它就会阻塞等待。

  1. public class TestC {
  2. // 定义共享资源区
  3. class Resource{
  4. private BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);
  5. public void put() throws Exception{
  6. queue.put(1);
  7. System.out.println(Thread.currentThread().getName()+" put:"+queue.size());
  8. }
  9. public void take() throws Exception{
  10. queue.take();
  11. System.out.println(Thread.currentThread().getName()+" take:"+queue.size());
  12. }
  13. }
  14. ...
  15. 定义生产者、消费者、main 方法与上面一样,不再赘述。
  16. }

代理模式

正向代理:客户端通过代理服务器访问受限的服务器,此时代理服务器作为客户端的代理,向服务器发起请求。
作用:

  • 突破访问限制,通过代理方式访问被封的网站
  • 隐藏客户端真实 IP

反向代理:代理服务器接收客户端请求,分发到内部服务器,响应内部服务器的结果给客户端;此时代理服务器是作为内部服务器的代理,客户端不知道真正调用的哪个服务器。
作用:

  • 负载均衡,将请求分散到内部的各个服务器中
  • 保证内部服务器安全,客户端只知道代理服务器的地址,不知道内部服务器的地址