设计模式:再谈单例模式

单例模式可以说是每个人问设计模式都会脱口而出的几个设计模式之一,为什么之前写过一次了,现在我又写一遍。肯定不是重新写一遍怎么是设计模式。

这次写的单例模式主要围绕的关键词有,延迟加载,高并发,线程安全,为什么说用枚举的单例模式优雅且怎么做到的。。。。

借用争哥这个id生成器的例子:
先来5个实现单例模式的方式
(1)饿汉模式
(2)懒汉模式
(3)双重检测
(4)静态内部类
(5)枚举

饿汉(我不管怎么样,我上来就new一个对象,不能饿着我)


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static final IdGenerator instance = new IdGenerator();
  private IdGenerator() {}
  public static IdGenerator getInstance() {
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

懒汉(用到的时候才去new对象,这里要注意跟我以前写的不一样这个是线程安全的懒汉写法)


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private IdGenerator() {}
  public static synchronized IdGenerator getInstance() {
    if (instance == null) {
      instance = new IdGenerator();
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

(3)双重检测(就是我不想饿汉一样上来就new对象,想做到延迟加载,能省一点空间就省一点,但又不像懒汉一样,想支持高并发-----------于是搞了个双重检测,但是他有自己的问题就是指令重排的问题)


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private IdGenerator() {}
  public static IdGenerator getInstance() {
    if (instance == null) {
      synchronized(IdGenerator.class) { // 此处为类级别的锁
        if (instance == null) {
          instance = new IdGenerator();
        }
      }
    }
    return instance;
  }
  public long getId() { 
    return id.incrementAndGet();
  }
}

(4)静态内部类(这个可以认为是上一个“双重检测”的改写,双重检测问题在于,可能多个线程,刚把这个id生成器还没new出来,指令重拍别的线程可能就先用上了。就是new 使用对象这两个操作1和2被打乱顺序了,解决方案除了可以用voliate这种增加线程间可见性,防止顺序被打乱以外。可以通过内部类来解决,加载本类的时候才会加载内部类,但不会生成对象,只有本类的方法调用时才能获取到。)


public class IdGenerator { 
  private AtomicLong id = new AtomicLong(0);
  private IdGenerator() {}

  private static class SingletonHolder{
    private static final IdGenerator instance = new IdGenerator();
  }
  
  public static IdGenerator getInstance() {
    return SingletonHolder.instance;
  }
 
  public long getId() { 
    return id.incrementAndGet();
  }
}

5 枚举 (枚举这个可太有意思了,这个枚举不太了解可以看一下这几个
美国知乎关于class,interface ,enum的区别
算是利用了枚举的特性:
An enum is a special “class” that represents a group of constants (unchangeable variables, like final variables).


public enum IdGenerator {
  INSTANCE;
  private AtomicLong id = new AtomicLong(0);
 
  public long getId() { 
    return id.incrementAndGet();
  }
}

谈一谈单例模式不好的地方?
单例模式反模式?我们使用单例模式都是为了解耦增加扩展性,单例这个就显得有点特立独行。

单例对 OOP 特性的支持不友好:他也不是不支持这OOP的封装、抽象、继承、多态,但你单独使用单例模式实现就会很怪

隐藏类之间的关系,可读性降低:单例直接返回一个对象,你就用就完了,但是这个会隐藏我们对于类与类之间的关系,可读性很差,我不想跳到一个类一堆方法里一步一步去跳转找到最后在哪创建的。

不支持有参数的构造函数,不灵活针对前面1,2,3点的一个补充。

单例模式的替代方案?
简单的是静态方法,觉得不够灵活,那就工厂模式,IOC容器(spring)

集群的单例模式?
我们谈的单例模式指的是进程间的单例,举个例子一个项目两台机都启动,能保证线程唯一吗?明显不能,因为两台机同一个项目属于不同的进程,类似的就是集群情况。我们前面这些都是保证,同一进程多线程的单例唯一的。

怎么来做,只能依靠外部力量,就是我们取一个对象,哪个进程来了先对他加个锁,看了小争哥的代码,他是用一个file这种竞争资源型的来解决,我觉得最好还是redis尤其是redission这种看门狗来实现。

这边贴的是小争哥的伪代码,对资源加锁


public class IdGenerator {
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略,比如文件地址*/);
  private static DistributedLock lock = new DistributedLock();
  
  private IdGenerator() {}

  public synchronized static IdGenerator getInstance() 
    if (instance == null) {
      lock.lock();
      instance = storage.load(IdGenerator.class);
    }
    return instance;
  }
  
  public synchroinzed void freeInstance() {
    storage.save(this, IdGeneator.class);
    instance = null; //释放对象
    lock.unlock();
  }
  
  public long getId() { 
    return id.incrementAndGet();
  }
}

// IdGenerator使用举例
IdGenerator idGeneator = IdGenerator.getInstance();
long id = idGenerator.getId();
idGenerator.freeInstance();

有多例模式吗?
肯定有,但是也是限制的,3例模式就是最多3个这种
代码演示也借用别人的,大概意思就是类似饿汉模式,搞了三个塞进Map里面然后随机取


public class BackendServer {
  private long serverNo;
  private String serverAddress;

  private static final int SERVER_COUNT = 3;
  private static final Map<Long, BackendServer> serverInstances = new HashMap<>();

  static {
    serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
    serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
    serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
  }

  private BackendServer(long serverNo, String serverAddress) {
    this.serverNo = serverNo;
    this.serverAddress = serverAddress;
  }

  public BackendServer getInstance(long serverNo) {
    return serverInstances.get(serverNo);
  }

  public BackendServer getRandomInstance() {
    Random r = new Random();
    int no = r.nextInt(SERVER_COUNT)+1;
    return serverInstances.get(no);
  }
}

这回单例应该算是完整了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值