一篇博客读懂设计模式之---单例模式
一。 单例模式
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
总体来说,单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
有以下的特点:(eg。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。为了避免不一致状态)
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
优缺点
优点: (频繁new同一个)
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
3.提供了对唯一实例的受控访问。
4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
5.允许可变数目的实例。
6.避免对共享资源的多重占用。
缺点: (保存状态,扩展职责)
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
1)*懒汉式单例:Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
ps:以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton1 {
//1、第一步先将构造方法私有化
private Singleton1(){}
//2、然后声明一个静态变量保存单例的引用
private static Singleton1 single = null;
//3、通过提供一个静态方法来获得单例的引用
//不安全的
public static Singleton1 getInstance(){
if (single == null){
single = new Singleton1();
}
return single;
}
}
//懒汉式单例.保证线程安全
public class Singleton2 {
//1、第一步先将构造方法私有化
private Singleton2(){}
//2、然后声明一个静态变量保存单例的引用
private static Singleton2 single = null;
//3、通过提供一个静态方法来获得单例的引用
//为了保证多线程环境下正确访问,给方法加上同步锁synchronized
//慎用 synchronized 关键字,阻塞,性能非常低下的
//加上synchronized关键字以后,对于getInstance()方法来说,它始终单线程来访问
//没有充分利用上我们的计算机资源,造成资源的浪费
public static synchronized Singleton2 getInstance(){
if(single == null){
single = new Singleton2();
}
return single;
}
}
//懒汉式单例.双重锁检查
public class Singleton3 {
//1、第一步先将构造方法私有化
private Singleton3(){}
//2、然后声明一个静态变量保存单例的引用
private static Singleton3 single = null;
//3、通过提供一个静态方法来获得单例的引用
//为了保证多线程环境下的另一种实现方式,双重锁检查
//性能,第一次的时候
public static Singleton3 getInstance(){
synchronized (Singleton3.class){
if (single == null){
single = new Singleton3();
}
return single;
}
}
}
2)*饿汉式单例:(饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。)
public class Singleton4 {
/**
* 懒汉式单例
* 最常用的一种模式就是这种
*/
//1. 私有化构造器
//相当于有一个默认的public的无参的构造方法,就意味着在代码中随时都可以new出来
private Singleton4(){}
//2. 生成一个静态内部构造类
//private 私有的保证别人不能修改
//static 保证全局唯一
private static class LazyLoader{
//final 为了防止内部误操作,代理模式,GgLib的代理模式
//静态内部类是为了防止反射获取属性
private static final Singleton4 INSTANCE = new Singleton4();
}
//3、同样提供静态方法获取实例
//final 确保别人不能覆盖
public static Singleton4 getInstance(){
//【静态方法】中的逻辑,是要在用户调用的时候才开始执行的
//方法中实现逻辑需要分配内存,也是调用时才分配的
//【区别】与静态块的最大区别
return LazyLoader.INSTANCE;
}
}
还有一种注册式:
//类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Singleton5 {
private static Map<String,Singleton5> map = new HashMap<>();
static {
Singleton5 single = new Singleton5();
map.put(single.getClass().getName(), single);
}
//静态工厂方法,返还此类惟一的实例
private Singleton5(){}
public static Singleton5 getInstance(String name) throws ClassNotFoundException {
if(name == null){
name = Singleton5.class.getName();
}
if(map.get(name) == null){
try{
map.put(name,(Singleton5)Class.forName(name).newInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
return map.get(name);
}
}
3) 饿汉式和懒汉式区别(重点)
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是下面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
1. 在getInstance方法上加同步
2. 双重检查锁定:
3. 静态内部类:
<span style="color:#3333ff">public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
} </span>
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
线程安全:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。
以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printInfo() {
System.out.println("the name is " + name);
}
}
public class TMain {
public static void main(String[] args){
TestStream ts1 = TestSingleton.getInstance();
ts1.setName("jason");
TestStream ts2 = TestSingleton.getInstance();
ts2.setName("0539");
ts1.printInfo();
ts2.printInfo();
if(ts1 == ts2){
System.out.println("创建的是同一个实例");
}else{
System.out.println("创建的不是同一个实例");
}
}
}
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。
对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。