单例模式,是在实际工作和面试中比较常见的模式,下面我们来学习单例模式的几种实现方法。
一、定义
首先看定义,单例模式:保证在软件系统中,对于某个类,只有一个对象实例,并且只提供一种获取该类实例的方法。
二、饿汉式
饿汉式是一种常见且直观的单例实现。实现步骤:
1、构造器私有化,防止外部new;
2、本类内部创建对象实例;
3、提供一个公有的静态方法,返回对象实例;
实现代码:
public class SingletonTest1 {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1 == singleton2);
System.out.println("singleton1:" + singleton1.hashCode());
System.out.println("singleton2:" + singleton2.hashCode());
}
}
// 饿汉式
class Singleton {
// 1、构造器私有化,防止外部能new
private Singleton() { }
// 2、本类内部创建对象实例,私有静态变量
private final static Singleton instance = new Singleton();
// 3、提供一个外部调用实例的静态方法
public static Singleton getInstance() {
return instance;
}
}
输出:
根据输出的哈希值,可以看见两次获取的是同一个对象。
饿汉式的优缺点:
1、类加载的时候就完成了创建实例,避免线程同步问题;
2、从未使用该类也会被实例化,造成内存浪费;
三、懒汉式(线程不安全)
上面说到的饿汉式实现,当这个类不是常用类时,会有个内存浪费的问题。我们可以进行优化,当使用到这个类时,才进行类的实例化。也就是延迟创建对象,即懒汉式。
实现代码:
// 懒汉式(线程不安全)
class Singleton {
// 1、构造器私有化,防止外部能new
private Singleton() { }
// 2、私有实例变量
private static Singleton instance;
// 3、提供一个外部调用实例的静态方法,当使用到时,才实例化对象
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
上面的代码实现了延迟创建对象,但存在多线程安全问题。当多个线程访问时,可能会同时进入到if(instance == null)方法内,导致实例化出多个对象。
四、懒汉式(线程安全)
解决线程安全问题,第一个想到的是加synchronized。
实现代码:
// 懒汉式(线程安全)
class Singleton {
// 1、构造器私有化,防止外部能new
private Singleton() { }
// 2、私有实例变量
private static Singleton instance;
// 3、提供一个外部调用实例的静态方法,当使用到时,才实例化对象。并用synchronized解决同步问题。
public synchronized static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
使用synchronized很好的解决了同步问题。但思考下,synchronized是加在方法上的,每一次的访问都要进行同步阻塞,影响效率。其实只需要在第一次初始化时,才需要同步。
五、双重检查
上面的懒汉式,虽然延迟加载,线程安全,但效率不高,不是理想的实现方式。下面继续优化,用双重检查实现。
实现代码:
// 双重检查,推荐使用
class Singleton {
// 1、构造器私有化,防止外部能new
private Singleton() { }
// 2、私有实例变量,增加volatile,读取最后修改的值
private static volatile Singleton instance;
// 3、提供一个外部调用实例的静态方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查是我们工作中推荐的单例实现方式。优点:
1、双重检查是多线程开发中经常使用的,如代码所示,进行了两次instance非空判断,并用volatile修饰instance,保证了线程安全。
2、实例化代码只执行了一次,而且不会重复访问同步方法块,效率高。
3、达到延迟加载的效果。
六、静态内部类
扩展,推荐的方式除了双重检查,还有种实现方式——静态内部类。
1、当外部类被装载时,静态内部类不会被装载,达到延迟加载的效果;
2、当获取静态内部类属性时,静态内部类属性只实例化一次,并且线程安全的;因为JVM类装载是线程安全的;
实现代码:
// 静态内部类,推荐使用
class Singleton {
// 1、构造器私有化,防止外部能new
private Singleton() { }
// 2、私有实例变量,增加volatile,读取最后修改的值
private static volatile Singleton instance;
// 3、静态内部类,属性是Singleton的实例
private static class SingletonInstance {
private static Singleton INSTANCE = new Singleton();
}
// 4、提供一个外部调用实例的静态方法
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
七、枚举
继续扩展,另一种推荐实现——枚举。
1、利用枚举可以很轻松的实现单例模式,不仅能实现多线程安全,而且能防止反序列化对象被重新创建;
2、这种方式是effective java作者josh Bloch提倡的方式;
实现代码:
public class SingletonTest6 {
public static void main(String[] args) {
Singleton singleton1 = Singleton.INSTANCE;
Singleton singleton2 = Singleton.INSTANCE;
System.out.println(singleton1 == singleton2);
System.out.println("singleton1:" + singleton1.hashCode());
System.out.println("singleton2:" + singleton2.hashCode());
}
}
// 枚举,推荐使用
enum Singleton {
INSTANCE;
}
以上就是单例模式的常见实现方法了。欢迎讨论,共同学习。