单例模式的定义
单例模式就是确保某一个类只有一个实例,并且提供一个全局访问点。
单例模式的常用
1.Windows的任务管理器
2.Windows的回收站,也是一个单例应用
3.项目中的读取配置文件的对象
4.数据库的连接池
5.Servlet中的Application Servlet
6.Spring中的Bean默认也是单例的
7.SpringMVC Struts中的控制器
单例模式的分类
饿汉式
线程安全 调用率高 但是不能延迟加载
懒汉式
线程安全 调用率不高 但是可以延迟加载
代码说明
饿汉式
public class Singleton {
// 提供静态的全局变量,作为访问该类的实例入口
private static Singleton singleton = new Singleton();
/**
* 构造器私有,无法创建对象
*/
private Singleton() {
}
/**
* 对外提供get方法获取该类的实例
* @return
*/
public static Singleton getSingleton() {
return singleton;
}
}
懒汉式
public class Singleton {
// 提供静态的全局变量,作为访问该类的实例入口
private static Singleton singleton = null;
/**
* 构造器私有,无法创建对象
*/
private Singleton() {
System.out.println("构造函数被调用了");
}
/**
* 对外提供get方法获取该类的实例
* @return
*/
public static Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
当全局变量初始化放到了实例化方法中,延迟产生对象。 当多个线程统一访问时,有可能出现线程不安全的情况。
解决方案A
只要getInstance()加上同步锁,,一个线程必须等待另外一个线程创建完后才能使用这个方法,这就保证了单利的唯一性。
加锁会导致synchronized修饰的同步块可是要比一般的代码慢上几倍的!如果存在很多次的getInstance()调用,那性能问题就不得不考虑了。
public class Singleton {
// 提供静态的全局变量,作为访问该类的实例入口
private static Singleton singleton = null;
/**
* 构造器私有,无法创建对象
*/
private Singleton() {
System.out.println("构造函数被调用了");
}
/**
* 对外提供get方法获取该类的实例
* @return
*/
public synchronized static Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
解决方案B
这种模式叫做双重校验锁式分别在代码锁前后进行判空校验 ,双重校验锁式是线程安全的。
在JDK 5以后开始提供volatile关键字修饰变量。就可以实现线程安全的延迟初始化,这样的话,重排序就是会被禁止,在多线程的时候,CPU也有共享内存,我们加上了这个关键字了之后,所有线程就能看到共享内存的最新状态,保证了内存的可见性。
public class Singleton {
//提供静态的全局变量 作为访问该类实例的入口 但是这里不立即加载
private volatile static Singleton sh = null;
/**
* 构造器私有 无法创建对象
*/
private Singleton(){
System.out.println("被调用了");
}
public static Singleton getInstance() {
if(sh == null){
synchronized(Singleton.class){
if(sh == null){
sh = new Singleton();
}
}
}
return sh;
}
}
解决方案C
然而,如果没有在Jdk1.5之后,怎么办?
使用静态内部类的方式
public class Singleton {
private static class InnerClass {
private static Singleton staticInnerClassSingleton = new Singleton();
}
public static Singleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
private Singleton(){
}
}
单例模式的优缺点
优点:
节约了系统资源。由于系统中只存在一个实例对象,对与一些需要频繁创建和销毁对象的系统而言,单例模式无疑节约了系统资源和提高了系统的性能。
单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
缺点:
由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
单例类的职责过重,在一定程度上违背了“单一职责原则”。
使用场景
系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
总结
单例模式中确保一个类最多只有一个实例,构造器私有,而且必须要提供实例的全局访问点,单例模式可能会因为多线程的问题带来的安全隐患。