直接上正题
(不推荐)懒汉式写法(线程不安全)
下面写法在单线程可以使用;多线程可能会有线程安全问题。
比如有线程A和B,A运行到(1)处,转交CPU的使用权给B,B线程会创建一个实例,等到A拿回控制权,完全不知道B已经创建了实例,会再次运行new Singleton(),创建多余的实例,违反单例模式的定义。
public class Singleton{
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(singleton==null){//(1)
singleton=new Singleton();
}
return singleton;
}
}
(不推荐)懒汉式写法(线程安全)
这种写法使用同步保证线程安全,但是大部分业务场景是不需要同步的,造成了不必要的同步开销,不建议这种方式。
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if(singleton==null) {
singleton=new Singleton();
}
return singleton;
}
}
(推荐)懒汉式写法(双重校验)
使用双重校验的方式,通过加锁使只有一个进程进入创建实例。
还用volative防止指令重排和实现对其他线程可见性
public class Singleton{
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if(singleton==null) {
synchronized(Singleton.class) {
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
(不推荐)饿汉式写法
这种方式在类加载时就完成实例的初始化,所以类加载比较慢,但是正是由于使用类加载机制也就避免了线程安全的问题。
如果是静态资源导致了类加载,这时可能并不需要实例化,也就是没有实现懒加载的效果。
public class Singleton {
private static Singleton singleton=new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
(推荐)静态内部类写法
在类加载的时候并不会创建实例,只有在第一次调用getInstance()才会创建实例,实现懒加载。
至于线程安全,可以这么理解,JVM会保证一个类加载器在多线程的环境下被正确的加锁、同步。当多个线程同时去初始化一个类,只有一个线程可以执行类构造器,其他线程会阻塞,直到活动线程执行完类构造器。
要注意的是在活动线程执行完类构造器后,其他的线程也不会再执行类初始化,因为在同一个类加载器中,一个类型只会被初始化一次,这就保证了线程安全。
想要深入理解类加载机制、静态内部类的线程安全;点击这里
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE=new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
(推荐)枚举写法
这种方法是effective java作者提倡,不仅能避免多线程同步问题还能防止反序列化,并且在任何情况下都是单例;枚举单例的优点就是简单,虽然可读性并不是很高,但是感觉是大趋势。
想要深入理解,点击这里
public enum Singleton{
INSTANCE;
public Singleton getInstance() {
return INSTANCE;
}
}
个人比较喜欢这种方式,上面的可能比较抽象,放一下我常用的例子 User.java
public class User {
//私有化构造函数
private User() {}
//定义一个静态枚举类
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private User user;
//私有化枚举的构造函数
private SingletonEnum() {
User user=new User();
}
public User getUser() {
return user;
}
}
//对外暴露一个获取User对象的静态方法
public static User getUser() {
return SingletonEnum.INSTANCE.getUser();
}
}
(不推荐)容器的写法
容器写法可以将多种的单例进行统一管理,在使用时根据key获取对应实例对象。
这种方式使用时可以通过统一的接口进行获取操作,但是具体实现是隐藏的,由于hashMap在存值时会有一个key存多个值的情况,就会导致取值时有安全风险,虽然最后获取的值一样但中间会经历值覆盖的过程,具体的可以自行debug查看。
感兴趣的可以 点击这里
public class Singleton{
private static Map<String, Object> singletonMap= new HashMap<String,Object>();
private Singleton() {
}
public static void putInstance(String key, Object instance) {
if (key.length()>0 && instance != null) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
}
public static getInstance(String key) {
return singletonMap.get(key) ;
}
}
总结
具体使用哪种方式还是要看项目的实际情况,主要考虑并发的复杂度以及资源损耗的接受程度。