一、单例设计模式
- 定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 主要解决:一个全局使用的类频繁地创建与销毁。
- 何时使用:当想控制实例数目,节省系统资源的时候。
- 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
- 关键代码:构造函数是私有的。
- 应用实例: 1、一个党只能有一个主席。2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
- 优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
- 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
单例模式分为饿汉式和懒汉式。
饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。
懒汉式单例模式:在类加载时不初始化。
1. 饿汉模式(线程安全)
- 饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。
/**
*饿汉式单例模式(线程安全)
**/
public class Singleton{
private static Singleton instance = new Singleton();
//私有构造方法
private Singleton(){}
//静态方法为调用者提供单利对象
public static Singleton getInstance(){
return instance;
}
}
类的构造函数定义为 private,保证其他类不能实例化此类,然后提供了一个静态实例并返回给调用者。
(1)好处 : 只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。
(2)缺点 : 很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。
2. 懒汉模式(线程不安全)
- 这里使用了懒加载模式,但是却存在致命的问题。当多个线程并行调用getInstance()的时候,就会创建多个实例,即在多线程下不能正常工作。
/**
* 懒汉式(线程不安全)
*/
public class Singleton
{
private static Singleton instance;
private Singleton(){}
private static Singleton getInstnce()
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
3. 懒汉模式(线程安全)
- 虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用getInstance()方法,但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。
/**
* 懒汉式(线程安全)
*/
public class Singleton
{
private static Singleton instance;
private Singleton(){}
private static synchronized Singleton getInstnce()
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
4. 双重检验锁
- 双重检验模式,是一种使用同步块加锁的方法。又称其为双重检查锁,因为会有两次检查instance == null,一次是在同步块外,一次是在同步块内。
- 为什么在同步块内还要检验一次。因为可能会有多个线程一起进入同步块外的if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public class Singleton
{
private static Singleton instance;
private Singleton(){}
private static Singleton getInstance()
{
if(instance == null)
{
synchronized(Singleton.class)
{
if(instance == null)
{
instance == new Singleton();
}
}
}
return instance;
}
}
- 使用volatile的主要原因是:禁止指令重排序优化。在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。
public class Singleton
{
private volatile static Singleton instance;
private Singleton(){}
private static Singleton getInstance()
{
if(instance == null)
{
synchronized(Singleton.class)
{
if(instance == null)
{
instance == new Singleton();
}
}
}
return instance;
}
}
5. 静态内部类(线程安全)
public class Singleton
{
//静态内部类
private static class SingletonHolder
{
private static final Singleton instance = new Singleton();
}
private Singleton(){}
//第一次调用getInstance方法时,才会去加载SingleHolder类,继而实例化instance
public static final Singleton getInstance()
{
return SingletonHolder.instance;
}
}
- 这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
6. 枚举(线程安全)
public class Singleton
{
//创建枚举默认就是线程安全,而且还能防止反序列化导致重新创建新的对象。
public enum EasySingleton
{
INSTANCE;
}
}
7. 测试各种单例模式的线程安全
package com.wang.singleton;
public class SingletonDemo implements Runnable
{
public static void main(String[] args)
{
SingletonDemo[] threads = new SingletonDemo[10];
for (int i = 0; i < threads.length; i++)
{
threads[i] = new SingletonDemo();
}
for (int i = 0; i < threads.length; i++)
{
new Thread(threads[i]).start();
}
}
@Override
public void run()
{
//System.out.println(Singleton.getInstance().hashCode());//饿汉式单例模式(线程安全)
//System.out.println(Singleton2.getInstance().hashCode());//懒汉式(线程不安全)
//System.out.println(Singleton3.getInstance().hashCode());//懒汉式(线程安全)
//System.out.println(Singleton4.getInstance().hashCode());//双重校验锁(线程安全)
//System.out.println(Singleton5.getInstance().hashCode());//静态内部类(线程安全)
//System.out.println(Singleton6.getInstance().hashCode());//静态代码块(线程安全)
System.out.println(Singleton7.INSTANCE.hashCode());//枚举(线程安全)
}
}