在日常学习和开发中,单例模式经常遇到,想必大家多多少少都有些了解,比如:在Spring中创建的Bean实例默认都是单例模式存在的
就是一个类只有一个实列,而且自行实例化(自己创建实例)并向整个系统提供这个实例。
a)只能有一个实例
b)必须自己创建自己的唯一实例
c)必须给所有其他对象提供这一实例
a)减少系统性能开销,因为只生成一个实例
b)确保所有对象都访问唯一实例
a)每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。
b)网站的计数器,一般也是采用单例模式实现,否则难以同步。
c)多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
d) 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
a)饿汉式优点:线程安全,调用效率高
b)饿汉式缺点:加载类时立即实例化,所以没有延时加载的优势;不管有没有用到这个实例都会创建,浪费资源
c)饿汉式代码演示:
public class Hungry {
//创建一个私有静态的变量,用来存对象
//此处直接实例化,在类加载的时候就完成了实例化并保存在类中
private static Hungry huangry=new Hungry();
//私有化构造方法,外部无法调用构造方法创建该对象
private Hungry() {
}
//创建公共静态的方法,提供给其他类调用来获取对象
public static Hungry getObject() {
return huangry;
}
}
a)线程不安全的懒汉式(不推荐使用):最基础的懒汉式代码实现,下面代码只适用单线程,在多线程下不安全
public class Lazy {
private static Lazy lazy;
private Lazy() {
}
//调用该方法时才生成实例
public static Lazy getObject() {
if(lazy==null) {
lazy=new Lazy();
}
return lazy;
}
}
b)线程安全的懒汉式(不推荐使用):在上一步的基础上加上synchronize锁,每一次执行时都要进行同步和判断,执行速度慢
public class Lazy {
private static Lazy lazy;
private Lazy() {
}
//使用synchronized关键字来确保只会生成单例,线程安全的
public static synchronized Lazy getObject() {
if(lazy==null) {
lazy=new Lazy();
}
return lazy;
}
}
在程序每次调用getInstance()方法时先不进行同步,而是在进入该方法后再去检查类实例是否存在,若不存在则进入接下来的同步代码块;进入同步代码块后将再次检查类实例是否存在,若不存在则创建一个新的实例,这样一来,就只需要在类实例初始化时进行一次同步判断即可,而非每次调用getInstance()方法时都进行同步判断,大大节省了时间。
将对象声明为volatitle后,重排序在多线程环境中将会被禁止
public class Singleton {
// 私有构造
private Singleton4() {}
//加上volatile关键字,禁止重排序
private static volatile Singleton single = null;
// 同步代码块的内部和外部都判断了实例是否为空,
//因为可能会有多个线程同时进入到同步代码块外的if判断中,
public static Singleton getInstance() {
if (single == null) {
synchronized (Singleton.class) {
//如果在同步代码块内部不进行判空的话,
//可能会初始化多个实例。
if (single == null) {
//JVM 的即时编译器中存在指令重排序的优化,single变量如果不加volatile修饰,可能会出错
single = new Singleton();
}
}
}
return single;
}
}
线程安全,但不支持延迟加载
//枚举式单例模式
public enum Singleton {
//这个枚举元素,本身就是单例对象
INSTANCE;
//添加自己需要的操作
public void print(){
System.out.println("hello world");
}
}
调用方法:
public class Test {
public static void main(String[] args) {
Singleton.INSTANCE.print();
}
}
资源利用最大化且线程安全
//在调用getInstance()才会加载,同时立即实例化对象,
//所有线程安全,且有延时加载的优势
public class Singleton {
private static class Instance{
//在静态内部类中实例化对象
private static final Singleton instance=new Singleton();
}
private Singleton() {
}
public static Singleton getInstance(){
return Instance.instance;
}
}
饿汉式:线程安全,效率也高,但浪费资源,没有延迟加载
懒汉式:有延迟加载,线程不安全,就算加上synchronize同步锁,线程安全了,但执行效率低
双重检查加锁(推荐使用):线程安全,效率高,一定要加上volatitle关键字,不然会因为JVM重排序偶尔会出现问题
枚举类(推荐使用):实现简单,枚举本身就是单例,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞!但没有延迟加载
静态内部类(推荐使用):线程安全,且有延时加载的优势
日常开发中如何选择哪种单例模式呢:
1,占用资源少,不需要延时加载,枚举式好于饿汉式;
2,占用资源多,需要延时加载,静态内部类式或双重检查加锁好于懒汉式;
欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感谢。