引言
这里举个例子,古代的时候,一般只会有一个皇帝,比如秦朝。可以这样理解,皇帝是一个类,秦始皇则是一个对象实例。只会有一个皇帝实例,不然就乱套了,这在代码设计的思想中,就被称之为单例模式(Singleton Pattern)。
单例模式的定义和使用场景
定义
单例模式,定义如下:
确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。
单例模式的使用场景
在一个系统中,要求一个类有且仅能有一个对象,如果出现多个对象就会出现一系列不可预知的错误,就可以采用单例模式了,具体如下:
1.定义了大量静态常量和静态方法的工具类;
2.需要生成唯一序列的环境;
3.需要频繁创建然后销毁的实例;
4.频繁访问数据库或文件的对象。
具体的应用场景:
1.网站的计数器,如果不是单例的话就无法实现同步;
2.Windows的任务管理器(无法打开两个任务管理器);
3.Web应用的配置对象的读取;
4.数据库连接池;
5.Web应用配置文件的读取。
单例模式的要素
如果想让一个类只拥有一个实例对象,很简单:
1.私有的构造方法,禁止外部访问;
2.私有静态引用指向实例;
3.将自己实例当做返回值的静态共有方法。
单例模式的具体实现
单例模式,在我们平常使用中,主要有两种实现方法:饿汉式、懒汉式。
饿汉式
何为饿汉式,饿汉,饥不择食,此处同义:在加载类的时候就会创建类的单例,并保存在类中。
代码实现如下:
public class SingleTon {
private static SingleTon instance = new SingleTon();
private SingleTon() {
}
public static SingleTon getInstance() {
return instance;
}
public void say() {
System.out.println("我是皇帝秦始皇");
}
}
因为实例对象由static修饰,所以在类加载的时候就会调用私有的构造方法,创建类的单例,保存在类中。
这样做,优点是:
1.借由JVM实现了线程安全;
2.因为在类加载时就创建了类的单例,调用速度会比较快。
缺点也很明显,因为类加载就会创建该类的单例,不管用户是否需要,可能我们永远不会用到getInstance方法,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化,这样做会占用大量的内存资源。
懒汉式
何为懒,就是延时,特点就是单例在类第一次被使用的时候才会构建,延时初始化。
代码实现如下:
public class SingleTon {
private static SingleTon instance = null;
private SingleTon() {
}
public static SingleTon getInstance() {
if (instance == null) {
//多个线程判断instance都为null时,在执行new操作时多线程会出现重复情况
instance = new SingleTon();
}
return instance;
}
public void say() {
System.out.println("我是皇帝秦始皇");
}
}
该方法相较于饿汉式的优点就是资源利用效率高,在不执行类的静态方法getInstance的时候,类的实例就不会被创建。
该方法的缺点在于,当系统压力增大,并发量增加的时候就可能会出现多个实例,当线程A执行到instance = new SingleTon()的时候,对象的创建是需要时间的,此时线程B执行到了if(instance == null),判断条件也为真,于是继续执行下去,线程A获得了一个对象,线程B也获得了一个对象,在内存中就出现了两个对象。
单例模式的额外扩展
解决线程不安全的问题,可以对方法进行同步加锁,对getInstance()进行同步,代码实现如下:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
但这样做也有缺点,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
也可以采用同步代码块的方式,代码如下:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
为了解决这个问题,可以采取双重加锁,就是在同步代码块内部在进行一次判断,杜绝这个问题的产生:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return instance;
}
}
这种方法在多线程的开发中被称之为Double-Check双重检查,这样实现单例模式的方法,不仅具备了懒汉式延时加载,资源占用较少的有点,也避免了线程不安全以及同步方法效率低的缺点,是比较推荐的方式。
这里采用了volatile关键字保证了instance的可见性,因为Java在运行过程中分主内存和工作内存,为了效率,都是从工作内存中去读取,于是就有可能存在主内存和工作内存不一样的情况,volatile关键字的加上避免了这种情况,但是volatile不可以保证原子性,具体的话可以参考一下网上关于volatile的解析,这里不做过多解释。
单例模式还有其他的实现方式,比如静态内部类、枚举等,在这里Po一下枚举的写法
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
这里可以参考Blog:
单例模式的八种写法比较
多例模式
一个类想要有多个对象,很简单,new关键字就可以实现,一个类如果想只有一个对象,则可以使用单例模式,但是一个类如果想产生指定个数的对象,又该如何实现呢?
这里提供一个思路,在类里定义一个静态int变量,用来表明最多能产生的实例数量,然后定义一个List用来存放产生的对象,然后在类的静态代码块里写生成对象的逻辑,这样就可以实现一个类产生指定个数的对象。(同样,这是在类加载的时候就产生实例的)
这里举一个皇帝类作为代码的时候,具体可参照《设计模式之禅》第七章《单例模式》。
代码实现如下:
public class Emperor {
private static int maxEmperorNum = 2;
private static int numOfEmperorNum = 0;
private static List<Emperor> emperorList = new ArrayList<>();
private static List<String> emperorNameList = new ArrayList<>();
static {
for (int i = 0; i < maxEmperorNum; i++) {
emperorList.add(new Emperor("皇" + (i+1) + "帝"));
}
}
private Emperor() {
}
private Emperor(String emperorName) {
emperorNameList.add(emperorName);
}
// 随机取得一个皇帝
public static Emperor getInstance() {
Random random = new Random();
int numOfEmperorNum = random.nextInt(maxEmperorNum);
return emperorList.get(numOfEmperorNum);
}
public void say() {
System.out.println(emperorNameList.get(numOfEmperorNum));
}
}
最佳实践
单例模式是23个设计模式中比较简单的模式,应用也很广泛,最最常见的,Spring中的Bean就是单例的。Spring可以管理这些Bean,决定他们什么时候创建,什么时候销毁。
网友评论