1、对单例模式的理解
–在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。为什么会设计成单例模式?这是为了避免多个不同的线程对同一个端口发出调用造成冲突。
–在Java程序中,单例模式是我们需要某个类产生唯一的一个对象,我们使用这一个对象就可以了,但是,在多线程运行时调用这个类,JVM中可能会产生多个对象,这会导致错误和出现不符合业务逻辑的现象。
单例模式有以下特点:
- 1 单例类只能有一个实例。
- 2 单例类必须自己创建自己的唯一实例。
- 3 单例类必须给所有其他对象提供这一实例。
上面说的没有问题,我自己理解的单例模式是:在整个工程运行时,对于这个类来说他只需要产生一个对象的现象/模式就做单例模式。当然这个只生成一个对象的条件/规则是开发者自己来定义,其他的工作就交给虚拟机去处理。那我们的任务和操作就很明确了,任务:让一个类只生成一个实例,操作:我们能做的事就是定义它的条件/规则。应该站在两个不同的角度来做这个事情,第一个是别人的类(我没办法修改的类),第二个是我自己写的类,该怎么让这两种类以单例模式运行。
2、懒汉式
懒汉式是在 第一次调用 的时候产生实例化对象。“我要了,它才造”。
public class Singleton {
private Singleton() {} // 将Singleton的构造方法限定为private避免了类在外部被实例化。如果不写的这个构造方法系统会默认自动补上Public Singleton(){},通过无参构造也可以创建对象。
private static Singleton single=null;
//静态工厂方法 :public和static 组成的方法修饰,说明此方法是在其他类中可被单例类自身调用产生对象的唯一方法Singleton.getInstance()。public对外, static 可直接被单例类自身调用。
public static Singleton getInstance() {
if (single == null) { //确保只能产生一个实例对象
single = new Singleton();
}
return single;
}
}
这个方法产生的实例在只有一个线程的程序中是安全的,一般在并发的多线程中极不安全,但是多线程又必须使用,就需要用同步和静态内部类来改进。
第一种:静态工厂法 + 同步
public class Singleton {
private Singleton() {} //无参构造私有化
private static Singleton single=null;
//静态工厂方法 + 同步方法
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
缺点:当用多次使用这种方法时就要频繁同步方法,虽然线程安全,但是执行访问效率低。
调用方法:Singleton . getInstance();
同步(synchronized): 可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这个段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。
第二种:双重检查加锁
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法 + 同步 + 双重检查
public static Singleton getInstance() {
if (singleton == null) { //第一次检查
synchronized (Singleton.class) { //同步代码块,同步对象是Singleton的对象,在这里先判断后再同步,这样能提高性能,因为对象存在时就不需要再执行同步。
if (singleton == null) { //第二次检查
singleton = new Singleton();
}
}
}
return singleton;
}
调用方法:Singleton . getInstance();
双重检查加锁:
1. 进入getInstance方法时,先检查实例是否存在,如果不存在才进行下面的同步块,第一重检查的作用是减少了多次在同步情况下进行判断所浪费的时间;
2. 进入同步块过后,第二次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,第二次检查的作用是,在线程同步后,确保其他线程没有创建或持有这个对象时,才能创建唯一的对象。
优点:减少使用同步的次数。
第三种:静态内部类
public class OutClass {
//内部类的实例与外部类的实例没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
private static class InnerClass{
//使用静态初始化器,由JVM来保证线程安全,详细见下
private static final OutClass instance = new OutClass();
}
private static OutClass outClass;
public static final OutClass getInstance(){
return InnerClass.instance;
}
}
}
调用方法:OutClass. getInstance();
创建过程: 当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个写法的优势: getInstance方法没有被同步,它只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
静态初始化器: 在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:
1. 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
2. 访问final字段时
3. 在创建线程之前创建对象时
4. 线程可以看见它将要处理的对象时
☆ 这三种单例方法的差别:
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能。
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
3、饿汉式
饿汉式是在 类初始化 时,已经产生实例化对象。“我要时,他已经造好了”,静待我来取。
public class Singleton{
private Singleton() {} //下面利用这个私有无参构造方法创建对象。注意:不能用public修饰
private static final Singleton single = new Singleton(); //final修饰的变量就是常量,这个常量不可修改了。因为 Single变量里存放的是对象的堆内存的地址值,所以只能初始化一次。
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}
调用方法:Singleton. getInstance();
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
☆ 饿汉式和懒汉式区别:
1.单例生成时机不同
饿汉式是单例类一旦加载,就初始化完成生成单例对象,保证getInstance的时候,单例是已经存在的了。
懒汉式可能延迟加载,只有当调用getInstance的时候,才回去初始化这个单例。
2.安全性不同
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题。
懒汉式本身是非线程安全的,需要上面几种方法才能实现线程安全。
3.性能
饿汉式在类创建的就已经实例化一个静态对象出来,不管之后会不会使用这个单例,这个对象都会占据一定的内存,但有利有弊,在第一次调用时速度也会更快,因为其资源已经初始化完成。
懒汉式可能延迟加载,因为在第一次使用该单例时才实例化出来对象,后面的运行效果和饿汉式相同。