单例模式

单例模式的定义

单例模式是一个比较基础的设计模式,定义是“Ensure a class has only one instance, and provide a global point of access to it”,可以理解为一个类只有一个实例,向整个系统提供这个实例。这个实例的特点是自己实例化,通过在Singleton类里面自己使用new Singleton()来实现的。

单例模式要求一个类只能内部创建一个对象,怎么实现呢?可以考虑构造函数,由于使用new关键字创建对象的时候,都会根据输入端参数调用不同的构造函数,所以我们可以把构造函数设置成private私有权限访问,就可以禁止外部创建对象了。这个单例类可以自己new出来一个对象,其他类对这个类的访问,可以通过getInstance()来获取同一个对象。

单例模式的 特点
单例模式只能有一个实例
单例类必须创建自己唯一的实例
单例类需要向其他类提供这个实例

单例模式的实现

  1. 懒汉式(线程不安全)
public class Singleton {
	private static Singleton instance;
	private Singleton() {}
	public static Singleton getInstance() {
        if(instance == null ) {
             instance = new Singleton();
        }
        return instance;
    }
}

懒汉式的特点是,开始声明一个静态对象,只有当用户第一次加载的时候才会进行对象的实例化,所以叫懒加载。
2. 懒汉式(线程安全)
多线程情况下,为了保证线程安全,可以在getInstance方法上加synchronized锁,保证线程安全

public class Singleton {
	private static Singleton instance;
	private Singleton() {}
	public static synchronized Singleton getInstance() {
        if(instance == null ) {
             instance = new Singleton();
        }
        return instance;
    }
}
  1. 饿汉式
    饿汉式在类加载的时候就完成了初始化,类加载的过程比较慢,不过获取对象的速度会比较快。饿汉式通过类加载机制,避免了多线程的同步问题。
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInsatance() {
        return instance;
    }
}
  1. 双重检查锁 (非线程安全)
    双重检查锁在获取instance实例的时候进行了两次判断是不是为空。
    第一次是为了避免不必要的同步,只有instance为空才加锁
    第二次是当instance为空,才创建实例
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInsatnce() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
           }
        }
        return instance;
    }
}

普通的双重检查锁在声明静态实例的时候,没有加volatile。这就导致一个问题。
因为在对象实例化的时候

instance = new Singleton();

这一行代码其实包括三个步骤:
1.分配对象的内存空间 memory = allocate();
2. 初始化对象 ctorInstance(memory)
3.设置instance指向刚分配的内存地址 instance = memory

在这个三个步骤中,编译器会重排序,导致第三步发生在第二步之前,这样就会导致,设置instance指向刚分配的内存地址,但是此时对象还没有分配。这样就出现问题了。

为了解决这个问题,可以使用volatile关键字,把instance声明称volatile类型的。
5. 双重检查锁 (volatile优化,线程安全)

public class Singleton {
   private static volatile Singleton instance;
   private Singleton() {}
   public static Singleton getInsatnce() {
       if(instance == null) {
           synchronized(Singleton.class) {
               if(instance == null) {
                   instance = new Singleton();
               }
          }
       }
       return instance;
   }
}

volatile的作用是禁止指令的重排序,这样就保证多线程情况下,第二步初始化发生在第三步之前,保证延迟初始化是线程安全的。

volatile的原理是:
有volatile修饰的共享变量在写操作的时候,会触发一些操作:
JVM会向处理器发送一条Lock前缀的指令,会把这个变量所在的缓存行的数据写到系统内存。
这是还有个问题,虽然写到了主内存,但是其他处理器缓存的值还是旧的值。解决办法就是,每个处理器会检查总线上的数据判断自己缓存的数据是不是过期了,如果发现自己缓存行对应的内存地址被修改了,就把当前处理器缓存行的数据设置为无效。如果需要对这个数据进行操作,就重新从主内存拉取数据。

单例模式的应用场景

网站的计数器,一般用单例模式实现,这样容易实现同步。
web应用的配置对象的读取,一般也使用单例模式,因为配置文件是共享的资源。
线程池设置为单例模式,因为一般系统中只需要一个线程池,多个线程公用一个线程池就可以了。
单例模式的线程池代码

首先在ThreadPool里面创建线程池,可以创建一个FixedThreadPool,创建固定的线程。(注意要把构造方法写成private类型的,保证不会被其他类实例化,只能在这个单例类内部实例化。)
private ThreadPool(int corepoolsize, int maximumpoolsize, long keepalivetime) {
    this.corepoolsize = corepoolsize;
    this.maximumpoolsize = maximumpoolsize;
    this.keepalivetime = keepalivetime;
}
public void executor(Runnable runnable) {
    if(runnable  == null) {
        return ;
    }
    if(mexecutor == null) {
        mexecutor = new ThreadPoolExecutor(corepoolsize,
            maximumpoolsize, 
            keepalivetime,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingDeque<Runnable>,
            Executor.defaultThreadFactory()k
            new ThreadPoolExecutor.AbortPolicy()
        );
    }
    mexecutor.executor(runnable);
}

然后对ThreadPool类进行实例化
public static ThreadPool getThreadPool() {
    if(mThreadPool == null ) {
        synchronized(ThreadPool.class) {
              if(mThreadPool == null) {
                  int cpuNum = Runtime.getRuntime().availableProcessors();// 获取处理器数量
                    int threadNum = cpuNum * 2 + 1;// 根据cpu数量,计算出合理的线程并发数
                    mThreadPool = new ThreadPool(threadNum, threadNum, 0L);
              }
        }
   }
}

参考文献

[1]秦小波 . 设计模式之禅 . 北京 . 机械工业出版社

单例模式的优缺点和使用场景

线程池的好处,单例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值