单例模式的特点:
- 构造函数私有化
- 定义静态方法返回当前对象
- 确保有且只有一个对象
- 确保在序列化和反序列化操作的过程当中保证是同一个对象
序列化和反序列化
序列化:将一个对象转化成为字节序列过程
反序列化:将字节序列转为对象过程
线程安全
- 如果你的代码所在的进程中有多个线程同时运行,而这些线程可能会同运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期是一样的,就是线程安全的。
- 或者说一个类或者程序所提供的接口对于线程是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。
第一种 懒汉式
这也是最基本的形式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这是最基本的形式,满足单例模式的基本特点:构造函数私有化、静态方法返回当前对象、在同一个虚拟机范围内有且只有一个对象,但是当进行多线程操作的时候很有可能会出现多个实例对象,假如两个线程A、B,A执行了if (instance==null)语句,它会认为单例对象没有创建,此时线程切到B也执行了同样的语句,B也认为单例对象没有创建,然后两个线程依次执行同步代码块,并分别创建了一个单例对象。这就是他最大的缺点,线程不安全
第二种 加锁懒汉式
可以多线程使用
public class Singleton {
private static Singleton instance=null;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
在第一种方式上加上了同步锁,使得在多线程的情况下可以使用。当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。第一线程发现实例没有创建,就去创建,第一线程结束代码后释放同步锁,第二线程才可以加上同步锁继续执行,由于第一个线程已经创建了实例,所以第二个线程不需要创建实例,这样就保证了多线程环境下只有一个实例。由于每次获取实例的时候都需要加锁,很耗时,这也是这种方式的最大的缺点。
第三种 双重校验锁
前后进行了两次实例为空的判断,属于懒汉式的最优模式
public class Singleton {
private static Singleton instance=null;
private Singleton(){
}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
加锁的懒汉模式看起来即解决了线程并发问题,又实现了延迟加载,然而它存在着性能问题,依然不够完美。双重校验锁式只有当instance为空的时候才需要加同步锁,这种方式比上一种更加优化
由于java内存模型允许“无序写入”会造成双重加锁单例模式失效这时要加入volatile关键字,但是这是在java 1.5之后才有用
最后变成这样:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
以上都是属于懒汉式的单例模式,就是在调用getInstance()才去进行实例化,所以称之为懒汉式
第四种 饿汉式
这种是饿汉式的最基本形态
public class Singleton {
private static Singleton instance=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
由于饿汉式是提前新建好实例的,所以天生线程安全。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,这就造成了内存的浪费。
第五种 静态内部类式
public class Singleton {
private Singleton(){
}
private static class SingletonHolder{
private final static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
这种方式也算是饿汉式的变种,同样具有饿汉式的线程安全的优点,但是比饿汉式更加优化的地方在于他在内部类中创建实例对象,他只有在内部类调用的时候才会新建实例,从而实现了懒汉式的延迟加载。也就是说这种方式既有懒汉式的延迟加载又有饿汉式的线程安全的优点,所以这种方式式目前最优方案。
第六种 枚举
从java 1.5之后枚举成为单例模式的最优选择
写法简单这是它最大的优点,如果你先前写过单例模式
public enum AnimalHelperSingleton {
INSTANCE;
private AnimalHelperSingleton(){
}
public Animal[] buildAnimalList(){
final Animal[] animals = new Animal[10];
animals[0] = new SimpleAnimal(Animal.AnimalClass.MAMMAL,
"Dog", true, Color.GRAY);
animals[1] = new SimpleAnimal(Animal.AnimalClass.MAMMAL,
"Cat", true, Color.YELLOW);
animals[2] = new SimpleAnimal(Animal.AnimalClass.AMPHIBIAN,
"Frog", true, Color.GREEN);
animals[3] = new SimpleAnimal(Animal.AnimalClass.BIRD,
"Crow", true, Color.BLACK);
animals[4] = new SimpleAnimal(Animal.AnimalClass.BIRD,
"Cardinal", true, Color.RED);
animals[5] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD,
"Mantis", false, Color.GREEN);
animals[6] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD,
"Spider", false, Color.ORANGE);
animals[7] = new SimpleAnimal(Animal.AnimalClass.MAMMAL,
"Tiger", true, Color.ORANGE);
animals[8] = new SimpleAnimal(Animal.AnimalClass.MAMMAL,
"Bear", true, Color.BLACK);
animals[9] = new SimpleAnimal(Animal.AnimalClass.BIRD,
"Owl", true, Color.BLACK);
return animals;
}
}
//调用
Animal[] animals = AnimalHelperSingleton.INSTANCE.buildAnimalList();