时间: 2019-02-21
参考地址: 单例模式的八种写法比较
一、单例模式的实现思路
- Singleton类只会生成一个实例. 该类定义了static字段(类的成员变量), 并将其初始化为Singleton类的实例.初始化行为仅
在该类被加载时进行一次. - Singleton类的构造函数是private的, 为了禁止从Singleton类外部调用构造函数.
总结:
- 定义private static Singleton INSTANCE;的成员变量;
- 构造函数私有.
- 一个public的静态获取单例的方法.
二、单例模式的八种写法
写法一: 饿汉式(静态常量)
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
public class Singleton1 {
// 1. 定义static成员变量
private final static Singleton1 singleton1 = new Singleton1();
// 2. 定义私有构造方法
private Singleton1() {
}
// 3. 静态方法获取单例.
public static Singleton1 getInstance() {
return singleton1;
}
}
写法二: 饿汉式(静态代码块)
将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
public class Singleton2 {
// 1. static 成员变量
private static Singleton2 singleton2;
// 2. 初始化
static {
singleton2 = new Singleton2();
}
// 3. 私有构造函数
private Singleton2() {
}
// 4. 静态方法获取单例
public static Singleton2 getInstance() {
return singleton2;
}
}
写法三: 懒汉式(线程不安全)
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
public class Singleton3 {
// 1. static 成员变量
private static Singleton3 singleton3;
// 2. 私有构造方法
private Singleton3() {
}
// 3. 静态方法获取单例.
public static Singleton3 getInstance() {
// 这步判断, 如果有2个线程同时进入, 则会产生2个实例, 就不是单例的了. 线程不安全.
if (null == singleton3) {
singleton3 = new Singleton3();
}
return singleton3;
}
}
写法四: 懒汉式(线程安全, synchronize同步方法)[效率低]
优点: 解决了线程不安全的问题.
缺点: 同步效率低.
public class Singleton4 {
// 1. static成员变量
private static Singleton4 singleton4;
// 2. 私有构造方法
private Singleton4() {
}
// 3. 静态同步方法获取单例. synchronized修饰, 多线程情形下要排队等待对象锁释放.
public static synchronized Singleton4 getInstance() {
if (null == singleton4) {
singleton4 = new Singleton4();
}
return singleton4;
}
}
写法五: 懒汉式(线程安全, 同步代码块)
要么效率低, 要么不安全
public class Singleton5 {
// 1. static成员变量
private static Singleton5 singleton5;
// 2. 私有构造方法
private Singleton5() {
}
// 3. 静态同步代码块方法获取单例. 效率低. 与用synchronized修饰方法一样.
public static Singleton5 getInstance1() {
synchronized (Singleton5.class) {
if (null == singleton5) {
return new Singleton5();
}
return singleton5;
}
}
// 3. 静态同步方法获取单例. null == sinleton5这一步无法避免会有线程安全问题
public static Singleton5 getInstance2() {
if (null == singleton5) { // 线程不安全
synchronized (Singleton5.class) {
return new Singleton5();
}
}
return singleton5;
}
}
写法六: DCL(双重检查锁机制 Double Check Lock)
public class Singleton6 {
// 1. static成员变量
private static Singleton6 singleton6;
// 2. 私有构造方法
private Singleton6(){
}
// 3. 静态方法获取单例. 使用双重检查
public static Singleton6 getInstance() {
if (null == singleton6) { // 第一重检查
synchronized (Singleton6.class) { // 锁Singleton6类.
if (null == singleton6) { // 第二重检查
return new Singleton6();
}
}
}
return singleton6;
}
}
写法七: 静态内部类
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
public class Singleton7 {
// 1. 在私有静态内部类中定义static final 成员变量并实例化
private static class Demo{
private static final Singleton7 INSTANCE = new Singleton7();
}
// 2. 私有构造方法
private Singleton7() {}
// 3. 静态方法获取单例
public static Singleton7 getInstance() {
return Demo.INSTANCE;
}
}
写法八: 用枚举实现单例模式
案例1
public enum Singleton8Enum {
INSTANCE;
public void getInstance() {
System.out.println("用枚举实现单例模式");
}
}
案例2
public enum DemoEnum {
// 理解: INSTANCE是DemoEnum的实例.
INSTANCE;
// 定义INSTANCE包含参数User.
private User instance;
// 枚举类的私有构造方法. 默认是private的.
DemoEnum() {
instance = new User();
}
// 获取枚举类的具体参数user
public User getInstance() {
return instance;
}
}
@Data
@Slf4j
public class User {
private String name;
private String address;
public User() {
}
public User(String name) {
this.name = name;
}
public User(String name, String address) {
this.name = name;
this.address = address;
}
public static void main(String[] args) {
// 心得-定义 User类可以用new User();获取很多不同的实例对象. 枚举是列举出有穷序列集.
// 定义枚举类, 里面只列举出一个值, 此值代表一个User类的实例. 通过getInstance()方法可以获取到INSTANCE的值: 一个User类的实例.
// Enum: 自由序列化, 线程安全, 保证单例
// Enum是由class实现的->enum作为一个类来实现单例;
// Enum是通过继承Enum类实现的, enum不能作为子类继承其他类.也不能被继承, 是final修饰的. 但是可以用来实现接口
// Enum有且仅有private的构造器. 防止外部的额外构造.
User user1 = DemoEnum.INSTANCE.getInstance();
User user2 = DemoEnum.INSTANCE.getInstance();
User user3 = DemoEnum.INSTANCE.getInstance();
User user4 = DemoEnum.INSTANCE.getInstance();
log.info("获取的实例哈希值:{}", user1.hashCode());
log.info("获取的实例哈希值:{}", user2.hashCode());
log.info("获取的实例哈希值:{}", user3.hashCode());
log.info("获取的实例哈希值:{}", user4.hashCode());
}
}
网友评论