1. 单例模式
定义:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的办法。单例模式的要点有两个:
- 一个类只能有一个实例
- 类必须自己创建这个实例并向整个系统提供这个实例
单例模式是所有设计模式中最简单的模式,它只包含一个类,即单例类:在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例
实现代码
public class Singleton{
private static Singleton instance=null;
//私有构造函数
private Singleton() {}
//静态方法,可以加入自定义控制,保证只产生一个实例
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
public class SingletonTest {
public static void main(String[] args) {
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.print(s1==s2); //结果为true
}
}
在单例模式的实现过程中,需要注意以下三点:
- 单例类的构造函数为私有(不允许自由创建该类的对象)
- 提供一个自身的静态私有成员变量(供缓存实例使用)
- 提供一个公有的静态方法(类的访问点,调用方法的只能是类,不能是对象)
单例模式优缺点
优点:
- 提供了对唯一实例的受控访问
- 由于在系统中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统的性能
- 允许可变项目的实例
缺点:
- 单例类的扩展困难
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
适用场景
- 系统只需要一个实例对象时
- 客户调用类的单个实例只允许使用一个公共访问点,不能通过其他途径访问该实例
注意: - 不要使用单例模式存取全局变量,最好将全局变量放到对应类的静态成员中
- 不要将数据库连接做成单例,因为一个系统可能有多个数据库连接
2. 饿汉式(立即加载)
立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法是直接在声明静态对象的时候new实例化。即立即加载/饿汉式是在调用方法前,实例已经被创建了。
public class Singleton{
//立即加载方式 == 饿汉模式
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance(){
//此版本为立即加载,getInstance()没有同步,所以可能出现线非程安全问题
return singleton;
}
}
3. 懒汉式(延迟加载)
延迟加载就是在调用get()方法时实例才被创建,常见的实现方法是在get()方法中进行new实例化。
public class Singleton{
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if(singleton != null){
}else{
singleton = new Singleton();
}
return singleton;
}
}
缺点:多线程环境下,根本不能保证单例的状态。
解决办法:
- 使用synchronized关键字(修饰getInstance()方法或使用同步代码块)
3.1 饿汉式使用DCL双检查锁机制
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance!= null){
}else{
synchronized(Singleton.class){
if(instance== null){
instance= new Singleton();
}
}
}
return instance;
}
}
这是一种懒汉的单例模式,使用时才创建对象,而且为了避免初始化操作的指令重排序,给instance加上了volatile。
为什么用了synchronized还要用volatile?具体来说就是synchronized虽然保证了原子性,但却没有保证指令重排序的正确性,会出现A线程执行初始化,但可能因为构造函数里面的操作太多了,所以A线程的instance实例还没有造出来,但已经被赋值了(即代码中2操作,先分配内存空间后构建对象)。
而B线程这时过来了(发现instance不为null),错以为instance已经被实例化出来,一用才发现instance尚未被初始化。要知道我们的线程虽然可以保证原子性,但程序可能是在多核CPU上执行。
参考链接:volatile