单例模式属于创建型模式,为保证全局只有一个对象,有类本身创建实例,并保证只创建一个实例。这种方式提供了唯一访问该对象的方式,不需要创建对象。
常见方式:
1.饿汉式
类加载到内存中,由JVM保证线程安全
/**
* 饿汉式
*
*/
public class Singleton01 {
private static final Singleton01 INSTANCE = new Singleton01();
/**
* 私有化构造器,不允许外部创建对象
*/
private Singleton01(){}
public static Singleton01 getInstance(){
return INSTANCE;
}
}
由于饿汉式,类加载到内存中就会创建对象,造成资源浪费,由此,产生了懒汉式
2.懒汉式
1)
/**
* 懒汉式 lazyLoading
*/
public class Singleton02 {
private static Singleton02 INSTANCE;
private Singleton02(){}
public static Singleton02 getInstance(){
if(INSTANCE != null){
INSTANCE = new Singleton02();
}
return INSTANCE;
}
}
但是,这种方式有个缺陷,在多线程并发操作下,并不能保证单例。
验证方式如下;
public class Singleton02 {
private static Singleton02 INSTANCE;
private Singleton02(){}
public static Singleton02 getInstance(){
if(INSTANCE == null){
try {
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
Thread.currentThread().interrupt();
}
INSTANCE = new Singleton02();
}
return INSTANCE;
}
public static void main(String [] args){
for (int i = 0;i< 100 ;i++){
new Thread(()->
System.out.println(Singleton02.getInstance().toString())
).start();
}
}
}
结果返回
为解决线程安全问题,懒汉式变种为:
2)线程安全的懒汉式
/**
* 线程安全的懒汉式
*/
public class Singleton03 {
private static Singleton03 INSTANCE;
private Singleton03(){}
public static synchronized Singleton03 getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton03();
}
return INSTANCE;
}
}
在之前的代码中,加上锁来保证线程安全。在方法上加锁,每次调用都要看是否加锁,去申请锁,造成效率降低。改造方式如下
public class Singleton04 {
private static volatile Singleton04 INSTANCE;
private Singleton04(){}
public static Singleton04 getInstance(){
if (INSTANCE == null) {
// 双重检查
synchronized (Singleton04.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton04();
}
}
}
return INSTANCE;
}
}
通过锁代码块的方式,保证线程安全。做双重检查的原因是只有实例未创建的情况下才在同步情况下创建对象,避免因等待造成资源浪费。volatile修饰的变量不会被缓存,解决线程重排序的问题。
3.内部类的方式
public class Singleton05 {
private Singleton05(){}
private static class SingletonInner{
private static final Singleton05 INSTANCE = new Singleton05();
}
public Singleton05 getInStance(){
return SingletonInner.INSTANCE;
}
}
避免了饿汉式的缺陷,同时又保证了线程安全。
4.枚举类的方式
/**
* 《Effective Java》
*/
public enum Singleton06 {
INSTANCE;
// 业务方法
public void doSoming(){
System.out.println("doSoming");
}
}
枚举类的方式,是《Effective Java》推荐的方式,不仅可以保证线程安全,还可以防止反序列化。是单例的最优解