1. 前言
单例模式是项目中经常使用的一种设计模式,其特点是整个项目中,只会有一个实例运行。想要创建一个单实例对象,需要以下几个条件:
1)单例类只能有一个实例。
2)单例类必须自己创建自己的唯一实例。
3)单例类必须给所有其他对象提供这一实例。
为了不让外部可以随意的创建对象实例,我们需要私有化构造方法,然后从类的内部进行对象的实例化。对象实例化之后,我们可以通过静态变量来存储这个对象,
创建单例模式有两种方式,一种是饿汉式,一种是懒汉式,下面会针对这两种方式进行讲解
2.饿汉式
饿汉式顾名思义,实例会在类加载之后直接初始化,不管你现在是否需要它。代码如下:
package com.mlkj.singleton;
/**
* 饿汉式 单例模式
*/
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton1(){
}
}
在 Singleton1 类被第一次加载的时候,就会初始化这个类的实例,这种方式最大的好处是简单粗暴,并且是线程安全的,final保证了这个实例是不可变的,当我们需要调用这个实例的时候,只需要以对象点的方式就能获取到这个实例就可以了。
另外一种实现饿汉式单例的思路是使用枚举。枚举的每一个属性都是一个实例,所以我们只需要在枚举类中只保留一个属性,就能实现单例模式。
package com.mlkj.singleton;
/**
* 饿汉式 单例模式 枚举实现
*/
public enum Singleton2 {
INSTANCE
}
枚举本身就是构造私有的。所以直接使用这种方式也能实现单例效果。
还有一种比较复杂的情况,当实例化对象的时候,需要传入参数。由于单例模式是通过类本身自己创建对象的,所以我们无法将参数传入到类中进行初始化。
package com.mlkj.singleton;
/**
* 饿汉式 复杂单例
*/
public class Singleton3 {
public static final Singleton3 INSTANCE = new Singleton3("张三");
private String name;
private Singleton3(String name){
this.name=name;
}
}
在这种情况下我们只能将参数写死,没有办法动态传入。这种情况下,可以使用properties配置文件的方式,在静态代码块中初始化实例
创建一个properties文件,然后将需要的属性配置好
然后再初始化的时候,再静态代码块内将配置文件的信息读取出来进行实例化。
package com.mlkj.singleton;
import java.io.IOException;
import java.util.Properties;
/**
* 饿汉式 复杂单例实现
*/
public class Singleton4 {
public static final Singleton4 INSTANCE ;
static {
Properties properties = new Properties();
//使用当前类加载器读取properties文件
try {
properties.load(Singleton4.class.getClassLoader().getResourceAsStream("test.properties"));
} catch (IOException e) {
e.printStackTrace();
}
INSTANCE = new Singleton4(properties.get("name").toString());
}
private String name;
private Singleton4(String name) {
this.name = name;
}
}
3.懒汉式
懒汉式的单例只会在我们需要的时候才进行初始化,懒汉式的单例需要将静态变量私有化,然后提供一个对外获取静态变量的方法。
package com.mlkj.singleton;
/**
* 懒汉式 单例实现
*/
public class Singleton5 {
private static Singleton5 INSTANCE;
private Singleton5() {
}
public static Singleton5 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton5();
}
return INSTANCE;
}
}
通过静态方法 getInstance()来对对象进行初始化操作。下次再请求的时候就直接将对象返回不在创建新的对象从而实现单例。但是这种情况下只适合单线程模式。再多线程模式下,往往会出现线程A执行到 INSTANCE == null 时被阻塞,线程B也执行到 INSTANCE == null时发现对象还没有创建,这时候线程B将对象创建了,然后线程A也被唤醒,又创建了一遍当前实例。
那么怎么解决这个问题呢,我们可以通过双重判断加锁的方式来解决
package com.mlkj.singleton;
/**
* 懒汉式 单例实现线程安全
*/
public class Singleton6 {
private static Singleton6 INSTANCE;
private Singleton6() {
}
public static Singleton6 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton6.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton6();
}
}
}
return INSTANCE;
}
}
第一个判空可以再类实例化后省去每次都加锁的开销,当需要初始化时,线程会先拿到锁,然后再判断一次类是否已经实例话过,如果没有就进行实例化。等第二个线程拿到锁以后,会发现类已经实例化过了,就不在进行实例化,从而实现线程安全。
不过这种方式虽然保证了线程的安全,但是代码有点多,那有没有办法既能保证线程安全,又能不写那么多代码呢?答案是有。我们可以使用静态内部类的方式对代码进行改造。
package com.mlkj.singleton;
/**
* 懒汉式 静态内部类的方式实现线程安全的单例
*/
public class Singleton7 {
private Singleton7() {
}
private static class Instance {
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance() {
return Instance.INSTANCE;
}
}
将原本再外部类的变量移到内部类中,然后再外部类中提供获取的接口。内部类并不会随着外部类的加载而加载。只有当调用内部类的时候,内部类才会被加载。这就保证了再需要的时候对类进行初始化操作,从而实现线程安全的单例模式。
4.总结
如果是饿汉式,枚举的方式最简单
如果是懒汉式,静态内部类的方式最简单
再实际情况中,可以根据自身项目的需要进行选择。
PS:文采不好多多包涵,有错误的地方欢迎指正