单例模式
概念
是一种常用的软件设计模式。简而言之就是确保一个类只有一个实例,并且提供一个全局的访问点(外部通过这个带你来访问该类的唯一实例)
单例模式要点主要有以下三点:
- 单例类只能有一个实例
- 单例类必须自己创建唯一的实例
- 单例类必须给所有其他对象提供这一实例。
两种单例模式
饿汉式单例
该创建单例的主要思想是:无论你需不需要这个对象,都在类初始化时直接创建实例对象,并且他是线程安全的。
饿汉式单例主要有三种创建方法,接下来我们来一一介绍:
直接实例化:
public class Singleton{
public static final Singleton INSTANCE = new Singleton();//直接创建
private Singleton(){}//私有化构造方法
public static Singleton getInstance(){
return INSTANCE;
}
}
枚举法:
在JDK1.5后提供了一种新的方法,枚举法来创建单例
public enum Singleton{
INSTANCE;
}
这种方法其实是实现单例模式的最佳方法,他更简洁,而且自动支持序列化机制,防止多次实例化。这种做法也是Effective Java的作者Josh Bloch所提倡的方法,这可以避免多线程同步的问题。
静态代码块法:
public class Singleton{
public static final Singleton INSTANCE;
static{
INSTANCE = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
懒汉式单例
public class Singleton{
private static Singleton INSTANCE;
private Singleton(){ //构造器私有化
}
public static Singleton getInstance(){ //静态方法获取实例对象
//先判断
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
要注意的是,该方式在单线程下没有问题,但是在多线程下并不安全,我们进行测试 打印出对象的hashcode可以发现这个问题:
/**
* 懒汉式
* 线程不安全
*/
public class Singleton {
private static Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if (INSTANCE == null) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr02();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Singleton.getInstance().hashCode());
}).start();
}
}
}
执行结果:
因此我们将上述代码进行改进,加入sychronized锁
public class Singleton{
private static Singleton INSTANCE;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
这种方法就很好的避免了线程安全问题,但是也有一个缺点,效率很低。
因此我们继续进行改进,在JDK1.5后提供了一种方法:双重校验锁(DLC,double-checked locking)
public class Singleton{
private static Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
这种双重锁的机制线程安全,并且在多线程的情况下可以保持高性能,但是编写起来比较复杂。
最后介绍一种静态内部类的方法
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
这种方法可以达到和双重锁一样的效果,但是实现更为简单。对静态与使用延迟初始化,应使用这种方式而不是双重锁的方式。 该方法只适用于静态域的情况,双重锁也可以在实力与需要延迟初始化时使用。
最后总结:一般情况下我们不建议使用第一种和只加一个锁的懒汉式,建议使用第一种的饿汉式。当涉及到反序列化创建对象是,可以尝试使用枚举方式。说到底,简而言之还是看你所遇到的情况再选择相对合适的创建方式。