核心:保证系统中的一个类只有唯一的一个实例,能为其他对象提供这一实例
Java中的单利主要是保证三点:
1.线程安全 2.不会因为序列化而产生新实例 3.防止反射攻击
常用的两种方式:懒汉式,饿汉式
先说一下懒汉式:
顾名思义,懒汉式是指在方法调用获取实例时才创建实例。
public class Peasant {
//私有无参构造
private Peasant() {}
//声明静态变量节省内存空间,始终用这一个变量
private static volatile Peasant singleton = null;
//静态工厂方法
public static Peasant getInstance() {
if (singleton == null) {
synchronized (Peasant.class) { //同步锁
if (singleton == null) {//二次校验
singleton = new Peasant();
}
}
}
return singleton;
}
}
为什么一定要私有无参构造呢?
因为避免类在外部被实例化,私有无参构造后Peasant的唯一实例只能getInstance()访问。自己去创建自己的实例。
为什么变量singleton 要加static修饰呢?
原因很简单,因为要保证单例,必须私有构造。私有构造后就不能对外提供方法获取该类的实例了。但又需要自己去创建自己的实例,这个时候只能通过类名去访问。而类名访问只能用static修饰,而static方法只能访问static成员。所以变量singleton 要加static,getInstance()方法同样需要加static。创建实例的时候Peasant.getInstance(),直接调用即可。
懒汉式如何保证线程安全呢?
在上面的代码中已经体现了,采用双检查锁机制来保证线程的安全。使用synchronized同步,并且进行二次校验。这里需要注意一下 我们使用了volatile来进行修饰。因为不使用volatile的双检查锁机制还是不能保证线程的安全。volatile用来保证变量更新的时候可以通知到其他线程,新的值能立即同步到主内存,以及每次使用前立即从主内存刷新. 当把变量声明为volatile后,编译器与运行时都会认为次变量是共享的.简单的来说被volatile修饰的变量,JVM读取的时候直接从内存读取,跳过CPU的缓存。而非volatile修饰的变量,线程在读取的时候首先从内存拷贝的CPU的缓存中,如果有多个CPU,多线程在运行的时候,那么每个线程可能被不同的CPU处理,线程访问这些变量时,其访问的是这些缓存的变量有可能,并不是内存中实际变量。
最后说一下饿汉式:
public class Peasant {
//私有无参构造
private Peasant() {}
//自行实例化
private static final Peasant singleton = new Peasant();
//静态工厂方法
public static Peasant getInstance() {
return singleton;
}
}
饿汉式在创建类的同时已经创建好一个静态对象,以后就不会改变了,所以天生线程就是安全的。
总结:
饿汉式:在类创建的同时就已经实例化一个静态对象,即便是你不使用这个对象,它也会占据一定的内存。但是其在第一次调用的时候速度也会很快,因为其资源已在初始化的时候就已经加载完成。线程是天生安全的。
懒汉式:相对饿汉式来说不使用的话并不会占据内存,但第一次调用的时候会进行初始化有延迟,初始化后就和饿汉一样了。就线程安全而言以上代码已解决,但同步之后会影响性能。
最后这里推荐一种适中的单利写法
public class Peasant {
private Peasant (){}
//静态内部类
private static class PeasantLode {
private static final Peasant MyPeasant = new Peasant();
}
public static final Peasant getPeasant() {
return PeasantLode.MyPeasant;
}
}
懒汉式通过调用静态内部类的方式进行初始化,这样既保证了线程的安全,又保证了内存的开销。