单例模式
所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。
定义
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
实现方式
懒汉式-线程不安全
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (single == null) ,并且此时 single 为 null,那么会有多个线程执行 single = new Singleton(); 语句,这将导致实例化多次 single。
public class Singleton {
private static Singleton single;
private Singleton() {
}
public static Singleton getSingle() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
饿汉式-线程安全
线程安全,比较常用,但容易产生垃圾,因为一开始就初始化,也丢失了延迟实例化带来的节约资源的好处。
public class Singleton {
private static Singleton single = new Singleton();
private Singleton (){
}
public static Singleton getSingle() {
return single;
}
}
懒汉式-线程安全
只需要对 getSingle() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 single。
但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 single 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用
public static synchronized Singleton getSingle() {
if (single == null) {
single = new Singleton();
}
return single;
}
双重校验锁(DCL)-线程安全
single 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 single 没有被实例化时,才需要进行加锁。
双重校验锁先判断 single 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
public class Singleton {
private volatile static Singleton single;
private Singleton() {
}
public static Singleton getSingle() {
if (single == null) {
synchronized (Singleton.class) {
if (single == null) {
single = new Singleton();
}
}
}
return single;
}
}
考虑下面的实现,也就是只使用了一个 if 语句。在 single == null 的情况下,如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 single = new Singleton(); 这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句:第一个 if 语句用来避免 single 已经被实例化之后的加锁操作,而第二个 if 语句进行了加锁,所以只能有一个线程进入,就不会出现 single == null 时两个线程同时进行实例化操作
if (single == null) {
synchronized (Singleton.class) {
single = new Singleton();
}
}
single 采用 volatile 关键字修饰也是很有必要的, single = new Singleton();
这段代码其实是分为三步执行:
- 为 single 分配内存空间
- 初始化 single
- 将 single 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getSingle() 后发现 single 不为空,因此返回 single,但此时 single 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
静态内部类实现
只有第一次调用 getSingle 方法时,虚拟机才加载 Inner 并初始化 instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
public class Singleton {
private Singleton() {
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
public static Singleton getSingle() {
return Inner.instance;
}
}
枚举实现
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。
- 枚举类隐藏了私有的构造器。
- 枚举类的域是相应类型的一个实例对象
public enum Singleton {
INSTANCE
//doSomething 该实例支持的行为
//可以省略此方法,通过Singleton.INSTANCE进行操作
public static Singleton getInstance() {
return Singleton.INSTANCE;
}
}
枚举实例在日常开发是很少使用的,就是很简单以导致可读性较差。
在以上所有的单例模式中,推荐静态内部类单例模式。主要是非常直观,即保证线程安全又保证唯一性。
众所周知,单例模式是创建型模式,都会新建一个实例。那么一个重要的问题就是反序列化,他会破坏单例模式。因此当实例被写入到文件到反序列化成实例时,我们需要重写readResolve方法,这样的话就会浅拷贝一份,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。
private Object readResolve() throws ObjectStreamException{
return single;
}
简单工厂
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
定义
简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
实现方式
public interface Product {
}
public class ConcreteProduct implements Product {
}
public class ConcreteProduct1 implements Product {
}
以下是简单工厂的实现,他被所需要实例化的类调用。
public class SimpleFactory {
public Product createProduct(int type) {
if (type == 1) {
return new ConcreteProduct();
} else {
return new ConcreteProduct1();
}
}
}
public class Client {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Product product = simpleFactory.createProduct(1);
// do something with the product
}
}
工厂方法
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
定义
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。
实现方式
public abstract class Factory {
abstract public Product factoryMethod();
public void doSomething() {
Product product = factoryMethod();
// do something with the product
}
}
public class ConcreteFactory extends Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
public class ConcreteFactory1 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct1();
}
}
原型模型
使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。
实现方式
public abstract class Prototype {
abstract Prototype myClone();
}
public class ConcretePrototype extends Prototype {
private String filed;
public ConcretePrototype(String filed) {
this.filed = filed;
}
@Override
Prototype myClone() {
return new ConcretePrototype(filed);
}
}
public class Client {
public static void main(String[] args) {
Prototype prototype = new ConcretePrototype("abc");
Prototype clone = prototype.myClone();
System.out.println(clone.toString());
}
}
此文章参考CS-Note