单例模式
单例模式属于创建型模式,它提供了一种创建对象的最佳方式。
单例模式提供一个单一的类,负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问这个对象的方式,可以直接访问,不需要实例化该类的对象。
那么单例模式有什么用途呢?
当我们想控制实例数从而节省系统资源或是存在一个类频繁的创建与销毁,就可以使用单例模式。
例模式的特点:
1.单例类只能有一个实例。
2.单例类必须自己创建自己唯一的实例。
3.单例类必须给所有其他对象提供这一实例。
单例模式的线程安全问题:
1.获取单例时,要保证不能产生多个实例对象。
2.在使用单例对象时,单例对象内的实例变量是会被多线程共享的,要注意竟态条件的产生。(竟态条件:多线程运行的时候,如果线程随着cpu调度的顺序不同,而出现不同的运算结果,那么我们说这段程序在多线程环境中,存在竟态条件,也称这段代码不是线程安全的代码。)
单例模式实现方法:饿汉单例模式和懒汉单例模式,下面我们来看一下这两种方式分别是如何实现的。
1.饿汉单例模式(立即加载的方式)
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
/*
*饿汉单例模式---------线程安全
*/
public class Singleton {
//创建私有构造方法,这样该类就不会在类外部被实例化
private Singleton(){}
//私有的静态对象
private static Singleton singleton = new Singleton();
//私有的静态工厂方法,保证只能得到一个实例
private static Singleton getInstance(){
return singleton;
}
}
2.懒汉式单例模式(延迟加载方式)
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
/*
* 懒汉式单例模式-----------线程不安全
*/
public class SingletonL {
//私有构造方法
private SingletonL(){}
//私有静态对象
private static SingletonL singletonL = null;
private static SingletonL getInstance(){
//判断系统是否已经有这个单例,如果有则返回,如果没有则创建
if (singletonL == null){
singletonL = new SingletonL();
}
return singletonL;
}
}
该例虽然用延迟加载方式实现了单例模式,但是在多线程下singleTonL 会被线程共享,当第一个线程通过if语句判断singleTonL为null时,new一个SingleTonL,如果在创建对象的同时另一个线程通过if语句判断singleTonL此时也为null,就会出现线程不安全。所以我们考虑使用synchronized加锁。
/*
* 懒汉式单例模式-----------线程安全
*/
public class SingletonL {
//私有构造方法
private SingletonL(){}
private static Object lock = new Object();
//私有静态对象
private static SingletonL singletonL = null;
private static SingletonL getInstance(){
//给判断语句加锁,保证线程安全
synchronized (lock) {
if (singletonL == null) {
singletonL = new SingletonL();
}
}
return singletonL;
}
}
此方法解决了线程安全问题,但是效率却很低。每一次都是加锁后才能判断系统是否存在这个实例,但当系统存在这个实例时我们不用创建实例对象,也就不会存在线程安全问题,因此我们使用双重检验。(不能只有一个判断语句,把锁放在判断语句内,这样也会导致线程不安全,因为有可能one线程进入判断语句还未加锁tow线程就进入判断语句)
/*
* 懒汉式单例模式-----------线程安全
*/
public class SingletonL {
//私有构造方法
private SingletonL(){}
private static Object lock = new Object();
//私有静态对象
private static SingletonL singletonL = null;
private static SingletonL getInstance(){
//双重校验锁 ,当系统不用创建实例时,就可以跳过加锁的步骤
if (singletonL == null) {
synchronized (lock) {
//给判断语句加锁,保证线程安全
if (singletonL == null) {
singletonL = new SingletonL();
}
}
}
return singletonL;
}
}
双重检验不仅解决了线程安全问题,也解决了不必要加锁时反复加锁效率低的问题。