保证一个类只有一个实例,并且提供一个全局访问点。最重要的就是保证构造器私有。
public class LazySingleton {
private static LazySingleton instance;
//构造器私有
private LazySingleton(){
}
//静态方法返回对象
public static LazySingleton getInstance() {
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
存在一个问题,如果是多线程环境下,会有可能出现实例化两个对象。因此可以通过加锁的方式解决.
public class LazySingleton {
private static LazySingleton instance;
//构造器私有
private LazySingleton(){
}
//加synchronized锁
public static synchronized LazySingleton getInstance() {
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
加锁解决了多线程条件下实例化多个对象的问题,但是牺牲了效率。可以进行优化。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){
}
//双重检测锁模式
//1、检查变量是否被初始化(不去获得锁),如果已被初始化则立即返回。2、获取锁。3、再次检查变量是否已经被初始化,如果还没被初始化就初始化一个对象
public static LazySingleton getInstance() {
if (instance == null){
synchronized (LazySingleton.class) {
if(instance == null){
instance = new LazySingleton();//java中new对象不是原子性操作
}
}
}
return instance;
}
}
但是这种模式下还存在问题,java的new操作并不是一个原子操作,大致分三步:1.在堆中开辟对象所需空间,分配内存地址、2.根据类加载的初始化顺序进行初始化、3.将内存地址返回给栈中的引用变量。如果发生指令重排,将后两步顺序颠倒,在多线程环境中就会发生使用未初始化的对象的问题。因此需要加volatile关键字,禁止指令重排。
? 本质上借助jvm类加载机制,保证实例的唯一性。但是比较浪费内存资源,因为一开始,无论是否需要都会加载。
//类加载过程
//1.加载二进制数据到内存中,生成对应的二进制的Class数据结构
//2.连接:a、验证 b、准备(给静态成员变量赋默认值)c、解析
//3.初始化:给静态变量赋初值
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
//构造器私有
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
关于饿汉模式中是否需要加final需要看情况而定,声明final的变量,必须在类加载完成时已经赋值。存在释放资源的情况下,如果需要重新使用这个单例,就必须存在重新初始化的过程,就不能加final,对于不需要释放资源的情况,可以加final。final static修饰的成员变量必须直接赋值或者在静态代码块中赋值。
//结合了懒汉模式和饿汉模式的优点:不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
public class InnerClassSingleton {
//构造器私有
private InnerClassSingleton(){
}
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
首先,我们getInstance()函数并不直接new创建对象,而是取的是InnerClassHolder里的instance对象。所以无论多少线程一起调用getInstance函数都只会返回同一个单例。当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建。那在这里又是怎么保证单例的呢?主要是虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步。只会有一个线程去执行类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。(而且唤醒之后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次)。但是无法传递外部参数。
通过反射方式可以破坏单例模式,通过反射获得单例类的构造函数,由于该构造函数是private的,通过setAccessible(true)指示反射的对象在使用时应该取消 Java 语言访问检查,使得私有的构造函数能够被访问,这样使得单例模式失效。可以对实例化次数进行统计,当大于一次时就就抛出异常,(当然也不安全,可以通过反射修改这个值)。然后对构造函数加上synchronized关键字防止多线程情况下实例出多个对象。clone()不会破坏单例模式,虽然clone()方法,是直接从内存区copy一个对象,但是单例的类不能实现cloneable接口,普通对象直接调用clone()会抛出异常。
原文:https://www.cnblogs.com/jiezao/p/13278452.html