1.什么是单例模式?
单例模式的概念:
它的基本概念是一个类只生成一个实例。
在一个系统中,JVM中,只能存在有一个对象的实例,所有程序的处理都只能调用这一个对象完成相关逻辑操作,单态模式有点在于减少了new关键字的调用,实例的初始化,节省了系统内存开销。单态模式的难点在于多线程的处理,写法很多,真正能够执行的,考虑的,难点在于线程的安全问题!!!
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
2.单例模式的使用案例
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
1.在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
这些应用都或多或少具有资源管理器的功能。
每台计算机可以有若干个打印机,但只能有一个Printer Spooler,
以避免两个打印作业同时输出到打印机中。
每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
---------------------
生活中的例子:
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
3.单例模式的类图
4.单例模式的用法
单例模式的实现有多种方式,如下所示:
1、懒汉式,线程不安全
是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
package Singleton;
public class LazySingleton {
/**懒汉式设计模式*/
private static LazySingleton lazySingleton;
private LazySingleton()
{}
public static LazySingleton getLazySingleton()
{
if(lazySingleton==null)
{
lazySingleton=new LazySingleton();
System.out.println("懒汉式线程不安全对象创建成功");
}
return lazySingleton;
}
2、懒汉式,线程安全
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
package Singleton;
public class LazySingletonSafe {
private static LazySingletonSafe lazySingletonSafe;
private LazySingletonSafe() {
}
public static synchronized LazySingletonSafe getLazySingletonSafe() {
if(lazySingletonSafe==null)
{
lazySingletonSafe=new LazySingletonSafe();
System.out.println("懒汉式线程安全对象创建成功");
}
return lazySingletonSafe;
}
}
3、饿汉式
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
package Singleton;
public class HardSingletonSafe {
private static HardSingletonSafe HardSingletonSafe;
private HardSingletonSafe()
{}
public static HardSingletonSafe getHardSingletonSafen()
{
System.out.println("饿汉式线程安全对象创建成功");
return HardSingletonSafe;
}
}
4、双检锁/双重校验锁(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
package Singleton;
public class doublecheckedlockingSingleton {
//双检锁/双重校验锁
private volatile static doublecheckedlockingSingleton doublecheckedlockingSingleton;
private doublecheckedlockingSingleton() {
}
public static doublecheckedlockingSingleton getdoublecheckedlockingSingleton() {
synchronized (doublecheckedlockingSingleton.class) {
if (doublecheckedlockingSingleton == null) {
doublecheckedlockingSingleton = new doublecheckedlockingSingleton();
System.out.println("双检锁/双重校验锁线程安全对象创建成功");
}
}
return doublecheckedlockingSingleton;
}
}
实现按效果:
留坑代填:
5、登记式/静态内部类
6、枚举
5.单例模式的优点和缺点
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
以及线程安全问题