单例模式
1、简介:
上下文只有一个实例
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
定义:
单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
2、优缺点
为什么要使用单例模式?单例模式的优点在哪里?
优点:
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
使用场景:
1. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
1. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
1. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
缺点:
- 使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
3、单例模式类型
3.1、饿汉式
//饿汉式单例模式
public class Hungry {
//可能会浪费资源;类一开始就加载了这些,根本没有使用到
private byte[] byte1 = new byte[1024*1024];
private byte[] byte2 = new byte[1024*1024];
private byte[] byte3 = new byte[1024*1024];
private byte[] byte4 = new byte[1024*1024];
private byte[] byte5 = new byte[1024*1024];
//构造器私有化,拒绝别人创建这个对象
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
这里面的代码存在什么样的问题呢? 可能会浪费空间
为什么呢?因为如果在该类中声明了许多内存空间,但却没有使用的话,就很浪费内存空间,因为饿汉式它是在程序启动的时候就已经创建好了。
3.2、懒汉式
3.2.1、懒汉式方式一:(线程不安全)
//懒汉式单例
public class LazySingle {
private LazySingle(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private static LazySingle lazySingle;
public static LazySingle getInstance(){
if (lazySingle == null){
lazySingle = new LazySingle();
}
return lazySingle;
}
//多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
getInstance();
}).start();
}
}
}
为什么会存在线程安全问题?
这是因为多个线程去调用getInstance()方法来获取LazySingle的实例,那么就有可能发生这样一种情况,当第一个线程在执行if(LazySingle== null)时,此时LazySingle是为null的进入语句。在还没有执行LazySingle= new LazySingle()时(此时instance是为null的),巧了,这时候第二个线程也进入了if(LazySingle== null)这个语句,因为之前进入这个语句的线程中还没有执行LazySingle= new LazySingle(),所以它也会执行lazySingle= new LazySingle()来实例化LazySingle对象,因为第二个线程也进入了if语句所以它会实例化LazySingle对象。
这样就导致了实例化了两个Singleton对象。
3.2.1、懒汉式方式二:(线程安全)
//懒汉式单例
public class LazySingle {
private LazySingle(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private static LazySingle lazySingle;
//线程安全
public static synchronized LazySingle getInstance(){
if (lazySingle == null){
lazySingle = new LazySingle();
}
return lazySingle;
}
}
说明:
该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
3.2.1、懒汉式方式三:(双重检查锁)
//懒汉式单例
public class LazySingle {
private LazySingle(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private static LazySingle lazySingle;
//线程安全
public static LazySingle getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (lazySingle == null){
synchronized (LazySingle.class){
//抢到锁之后再次判断是否为null
if (lazySingle == null){
lazySingle = new LazySingle();
}
}
}
return lazySingle;
}
}