单例模式:确保一个类最多只有一个实例,并提供一个全局访问点
首先说明一下该类图中的符号:
类:
使用三层矩形框表示
第一层:显示类的名称,如果是抽象类,则就用斜体显示
第二层:字段和属性
第三层:类的方法
“+”:表示public
“-”:表示private
“#”:表示protected
接口:
使用两层矩形框表示,与类图的区别主要是顶端有<
显示
第一层:接口的名称
第二层:接口的方法
继承类(extends):
用空心三角形+直线表示
实现接口(implements):
用空心三角形+虚线表示
关联(Association):
用实线箭头来表示
聚合(Aggregation):
用空心菱形+实线箭头来表示
聚合:表示一种弱的‘拥有’关系,体现的是A对象可以包含B,但B对象不是A对象的一部分,例如:公司和员工
组合(Composition):
用实心菱形+实线箭头来表示
组合:部分和整体的关系,并且生命周期是相同的。例如:人和手
依赖(Dependency):
用虚线箭头来表示
例如:动物和氧气
Singleton类称为单例类,通过使用private的构造函数,确保了在一个应用中只产生一个实例,并且是自行实例化的(在Singleton中自己使用new Singleton())。
单例模式的通用代码:
饿汉式:
package singletontest;
/**
* 单例模式(饿汉模式)
* 应用场合:有些对象只需要一个就足够了,如古代皇帝,老婆
* 作用:保证整个应用程序中某个实例有且只有一个
* 类型:懒汉模式,饿汉模式
* @author 张耀晖
*
*/
public class Singleton {
//1.将构造方法私有化,不允许外部直接创建对象
private Singleton(){
}
//2.创建类的唯一实例,使用private static修饰
private static Singleton singleton = new Singleton();
//3.提供一个用于获取实例的方法,使用public static修饰
public static Singleton getInstance(){
return singleton;
}
}
懒汉式:
package singletontest;
/**
* 懒汉模式
* 区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全
* 懒汉模式特点是加载类时比较快,但运行时获取对象速度比较慢,线程不安全
* @author 张耀晖
*
*/
public class Singleton2 {
//1.将构造方法私有化,不允许外边直接创建对象
private Singleton2(){
}
//2.声明类的唯一实例,使用private static修饰
private static Singleton2 singleton2;
//3.提供一个用于获取实例的方法,使用public static修饰
public static Singleton2 getInstance(){
if(singleton2==null){
singleton2 = new Singleton2();
}
return singleton2;
}
}
package singletontest;
public class Test {
public static void main(String[] args) {
//饿汉模式
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1==s2){
System.out.println("s1和s2是同一个实例");
}else{
System.out.println("s1和s2不是同一个实例");
}
//懒汉模式
Singleton2 s3 = Singleton2.getInstance();
Singleton2 s4 = Singleton2.getInstance();
if(s3==s4){
System.out.println("s3和s4是同一个实例");
}else{
System.out.println("s3和s4不是同一个实例");
}
}
}
注意:
懒汉式是存在线程不安全问题的。
懒汉式在低并发的情况下有可能不会出现问题,但是当高并发的情况下则可能在内存中出现多个实例。
eg:如果一个线程A执行到singleton2 = new Singleton2(),但还没有获得对象(对象初始化是需要时间的),第二个线程B也在执行,执行到singleton2==null的判断,那么线程B获得判断条件也是为真,于是继续运行下去,线程A获得一个对象,线程B也获得一个对象,在内存中就出现了两个对象。
解决懒汉式的线程不安全问题的方法:
在getInstance()方法前加synchronized关键字。不过这样的话只适应调用实例对象少的情况,如果调用实例对象比较频繁synchronized就会影响系统的执行效率。那么就建议使用饿汉式。
饿汉式也有一定的问题,在不使用类的实例对象的时候也会创建该类的实例对象,造成一定的资源浪费。
单例模式的优点
•由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的被创建、销毁,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显;
•由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制);
•单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
•单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
单例模式的缺点
•单例模式没有接口,扩展很困难,若要扩展,除了修改代码没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何的意义,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。
•单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
•单例模式与单一职责原则有冲突。一个类应该只实现一个的逻辑,而不关心它是否是单例的,决定它是不是要单例是环境决定的,单例模式把“要单例”和业务逻辑融合也在一个类中。
单例模式的使用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”时,则可以采用单例模式,具体的场景如下:
•要求生成唯一序列号的环境;
•在整个项目中需要有访问一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
•创建一个对象需要消耗的资源过多,如要访问IO、访问数据库等资源;
•需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式);
现在有一个问题:如果要求一个类只能产生固定数量(大于1个)的对象怎么办?
package com.test.singletonTest;
import java.util.ArrayList;
import java.util.Random;
/**
* 单例模式的扩展,产生固定数量的(大于1个)的实例对象
* 2015年9月17日 下午5:08:15
* @author 张耀晖
*
*/
public class SingletonTest {
private static int maxnum = 3;//定义最多能产生的实例对象的数量
private static ArrayList singletonList = new ArrayList();//定义一个集合,容纳所有的实例对象
private static ArrayList singletonNameList = new ArrayList();//定义一个集合,容纳所有对象的私有属性
//当前实例对象的标号
private static int countOfNum;
//产生所有的对象
static{
for (int i = 0; i < maxnum; i++) {
singletonList.add(new SingletonTest("实例对象:"+i+1));
}
}
// //将类中默认的无参构造方法重写为private,防止在外部创建该类的实例对象
// private SingletonTest(){
//
// }
//传入实例对象的名称,创建实例对象
private SingletonTest(String name){
singletonNameList.add(name);
}
//随机获得一个实例对象
public static SingletonTest getInstance(){
Random random = new Random();
countOfNum = random.nextInt(maxnum);
//随机取出一个实例对象
SingletonTest singletonTest = singletonList.get(countOfNum);
return singletonTest;
}
//获取实例对象的私有属性
public static String getSingletonName(){
String name = singletonNameList.get(countOfNum);
return name;
}
}