单例模式的定义
单例模式是一个比较基础的设计模式,定义是“Ensure a class has only one instance, and provide a global point of access to it”,可以理解为一个类只有一个实例,向整个系统提供这个实例。这个实例的特点是自己实例化,通过在Singleton类里面自己使用new Singleton()
来实现的。
单例模式要求一个类只能内部创建一个对象,怎么实现呢?可以考虑构造函数,由于使用new关键字创建对象的时候,都会根据输入端参数调用不同的构造函数,所以我们可以把构造函数设置成private私有权限访问,就可以禁止外部创建对象了。这个单例类可以自己new出来一个对象,其他类对这个类的访问,可以通过getInstance()来获取同一个对象。
单例模式的 特点
单例模式只能有一个实例
单例类必须创建自己唯一的实例
单例类需要向其他类提供这个实例
单例模式的实现
- 懒汉式(线程不安全)
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;
}
}
- 饿汉式
饿汉式在类加载的时候就完成了初始化,类加载的过程比较慢,不过获取对象的速度会比较快。饿汉式通过类加载机制,避免了多线程的同步问题。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInsatance() {
return instance;
}
}
- 双重检查锁 (非线程安全)
双重检查锁在获取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]秦小波 . 设计模式之禅 . 北京 . 机械工业出版社