所谓类的单例设计模式,就是采取一定的手段保证在整个软件系统中,某个类只能存在一个实例,并且该类只提供一个静态方法获取到该类的实例,不提供方法给其创建实例。
一般对于一些比较重量级的,而且有一个实例就够用的,我们会将其设计成为单例模式,用于减轻系统的压力。
单例模式的八种方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
一、饿汉式(静态常量)
public class Singleton1 {
// 1、私有化构造方法,使其不能通过构造方法创建实例
private Singleton1(){};
// 2、 类内部创建静态对象实例,
private final static Singleton1 instance = new Singleton1();
// 对外提供共有的静态方法,返回该对象的实例。
public static Singleton1 getInstance(){
return instance;
}
}
优缺点对比
优点:写法简单易懂,并且在类装载的时候就完成实例化,不会出现线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到懒加载的效果,如果该类实例从未使用过,那么就会造成资源浪费。
他通过classloder加载的时候就创建,可以避免线程安全问题,但是也是因为这一点,可能会导致内存浪费。
二、饿汉式(静态代码块)
public class Singleton2 {
// 1、私有化构造方法,使其不能通过构造方法创建实例
private Singleton2(){};
// 2、 类内部创建静态对象变量,
private final static Singleton2 instance;
// 3、通过静态代码块将其实例化,并且赋值给instance
static {
instance = new Singleton2();
}
// 对外提供共有的静态方法,返回该对象的实例。
public static Singleton2 getInstance(){
return instance;
}
}
这种方式和饿汉式:静态常量类似,都是通过classloder加载的时候完成实例的创建,这种方式也有可能造成资源浪费,同时他不会出现线程安全问题。
三、懒汉式(线程不安全)
import java.util.Objects;
public class Singleton3 {
// 1、私有化构造方法,使其不能通过构造方法创建实例
private Singleton3(){};
// 2、 类内部创建静态对象变量,
private static Singleton3 instance = null;
// 3、通过静态代码块将其实例化,并且赋值给instance
// 对外提供共有的静态方法,返回该对象的实例。
public static Singleton3 getInstance(){
// 判断该变量是否已经实例化,如果没有实例化,那么就就行实例化,并且返回
if(Objects.isNull(instance)){
instance = new Singleton3();
}
return instance;
}
}
优缺点对比
该中方式确实起到了懒加载的效果,我们在使用的时候判断,为null我们才实例化,但是这种只能是在单线程的情况下,多线程的情况下,那么他就会容易出现实例化多次,破坏了单例模式设计的初衷了。这种方式在实际开发中尽量不要使用
四、懒汉式(线程安全,同步方法:synchronized)
import java.util.Objects;
public class Singleton4 {
// 1、私有化构造方法,使其不能通过构造方法创建实例
private Singleton4(){};
// 2、 类内部创建静态对象变量,
private static Singleton4 instance = null;
// 3、通过静态代码块将其实例化,并且赋值给instance
// 对外提供共有的静态方法,返回该对象的实例。
public static synchronized Singleton4 getInstance(){
// 判断该变量是否已经实例化,如果没有实例化,那么就就行实例化,并且返回
if(Objects.isNull(instance)){
instance = new Singleton4();
}
return instance;
}
}
说明:这种方式可以起到懒加载的效果,同时也能够保证线程安全,但是,如果这个获取实例的方法比较复杂,还要做其他更多的事情,那么将此方法同步的话,会造成线程阻塞。而且,在每次获取这个实例的时候,都要加上同步,那么效率太过低下,在实际开发中不推荐使用
五、懒汉式(线程安全,同步代码块:synchronized)
import java.util.Objects;
public class Singleton5 {
// 1、私有化构造方法,使其不能通过构造方法创建实例
private Singleton5() {
}
// 2、 类内部创建静态对象变量,
private static Singleton5 instance = null;
// 3、通过静态代码块将其实例化,并且赋值给instance
// 对外提供共有的静态方法,返回该对象的实例。
public static Singleton5 getInstance() {
// 判断该变量是否已经实例化,如果没有实例化,那么就就行实例化,并且返回
// todoSomething
synchronized (Singleton5.class) {
if (Objects.isNull(instance)) {
instance = new Singleton5();
}
}
// todoSomething
return instance;
}
}
这种方式效率也是比较低下的,虽然没有锁方法了,但是也是没有都锁着代码块。
六、双重检查:推荐使用
import java.util.Objects;
public class Singleton5 {
// 1、私有化构造方法,使其不能通过构造方法创建实例
private Singleton5() {
}
// 2、 类内部创建静态对象变量,
private static volatile Singleton5 instance = null;
// 3、通过静态代码块将其实例化,并且赋值给instance
// 对外提供共有的静态方法,返回该对象的实例。
public static Singleton5 getInstance() {
// 第一重判断解决每次获取实例都同步方法或者代码块导致效率低的问题
if(Objects.isNull(instance)){
synchronized (Singleton5.class){
// 第二重判断解决了重复创建的问题,由于这个判断是在同步代码块中进行的,不会出现线程安全问题。
if(Objects.isNull(instance)){
instance = new Singleton5();
}
}
}
return instance;
}
}
说明:实现了懒加载,线程也是安全的,并且效率较高。所以推荐使用
七、静态内部类:推荐使用
public class Singleton7 {
// 1、私有化构造方法,使其不能通过构造方法创建实例
private Singleton7() {
}
private static class SingletonInstance {
private static final Singleton7 INSTANCE = new Singleton7();
}
// 2、在调用的时候去装载静态内部类,而且静态内部类只装载一次
public static Singleton7 getInstance() {
return SingletonInstance.INSTANCE;
}
}
说明:这种方式采用了JVM的类装载线程安全机制来保证了线程的安全问题,并且类只会装载一次,后面在进行获取的时候,我们使用final修饰的静态属性,JVM内部会进行优化,并且只会实例化一次,所以每次获取的时候效率就很高。这种方式推荐使用。
八、枚举类型,简单粗暴:推荐使用
public enum Singleton8 {
INSTANCE;
}
说明:借助jdk1.5添加的枚举来实现单例模式,不仅能够避免多线程同步问题,还能防止反序列化重新创建对象。这种方式推荐使用