单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
1、饿汉模式
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
}
public static getInstance(){
return instance;
}
}
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到类加载的效果。
2、懒汉模式(线程不安全)
public class Singleton{
private static Singleton instance;
private Singleton(){
}
public static getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
懒汉模式申明了一个静态对象,在用户第一次调用时初始化,虽然节约了资源,但第一次加载时需要实例化,反映稍慢一些,而且在多线程不能正常工作。
3、懒汉模式(线程安全)
public class Singleton{
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,而且大部分时候我们是用不到同步的,所以不建议用这种模式。
4、双重检查模式(DCL)
public class Singleton{
private volatile static Singleton instance;
private Singleton(){
}
public Singleton getSingle(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效,在《java并发编程实践》一书建议用静态内部类单例模式来替代DCL。
5、静态内部类单例模式
public class Singleton{
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder{
private static final Singleton sInstance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化sInstance ,这样不仅能确保线程安全也能保证Singleton类的唯一性,所以推荐使用静态内部类单例模式。
6、枚举单例
public enum Singleton{
INSTANCE;
public void doSomeThing){
}
}
关键字说明
-
final(最终的)
-
volatile (不变的)是 Java 提供的一种轻量级同步方案,但是不保证原子性
volatile 主要有两个作用,一是保证变量对其他线程的立即可见性;二是禁止指令重新排序。
立即可见性的实现原理:在某个线程的工作内存中,每次使用变量前,都必须从主存中获取最新的值,用来保证能看见其他线程对变量的所有修改;每个线程在完成对某变量的修改后,都要立即刷回主内存,用来保证其他线程可以立即看到变量的最新值。
禁止指令重排序的实现原理:普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,但是不能保证赋值操作的顺序与 Java 代码中的顺序一致。而 volatile 就是为了保证代码在编译前后顺序一致。保证了把赋值后的值刷到内存上这个操作在最后执行,前面的指令不能重排到这个操作之后(也就是前面的指令全都执行完了,最后才真正把新值刷到内存)
-
synchronized(同步)关键字
线程运行时拥有自己的栈空间,会在自己的栈空间运行,如果多线程间没有共享的数据也就是说多线程间并没有协作完成一件事情,每一个线程依次去读写这个共享变量,这样就不会有任何数据安全的问题,因为每个线程所操作的都是当前最新的版本数据 。 在Java关键字synchronized就具有使每个线程依次排队操作共享变量的功能。很显然,这种同步机制效率很低