定义: 确保要创建的类只有一个实例
- 对于频繁创建的对象,可以省略其创建时间,同时可以减轻GC的压力
- 避免对共享资源的多重占用
使用场景:
- 网站的计数器,一般也是采用单例模式实现,否则难以同步
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
- Windows的任务管理器
单例模式写法很多,这里列举以下三种:
1) 最简单的写法:使用枚举对象
public enum SingletonEnum {
SINGLETON;
}
使用枚举还可以避免反射获取类的私有构造方法从而创建不同的对象
2)懒汉模式
public class Singleton {
private Singleton() { //构造函数私有
}
//防止重排序
private static volatile Singleton singleton = null;
public static Singleton getSingleton() {
if(singleton == null) {
synchronized (Singleton.class) { //加类锁
if(singleton == null) { //双重检测
singleton = new Singleton();
}
}
}
return singleton;
}
}
注意点:
-
构造方法私有。在其他方法中就不可以创建该类对象,只能使用本类的静态方法
-
双重检查模式。
如果不使用第二个判断为null,那么假设有两个线程A和B,A线程拿到锁,进入同步代码块,在创建对象之前B线程通过了第一个判断,当A线程执行结束,B线程会进入同步代码块,接着创建对象。 -
volatile关键字的使用,防止重排序。
new对象过程:
step1:在堆上分配内存空间
step2: 对象初始化
step3: 将栈上的对象引用指向堆对象
不加volatile可能会造成先执行1,3,这样会造成A线程在完成1,3时,B线程来创建对象,发现singleton != null,直接将未初始化的对象返回。
3)使用静态内部类
public class Singleton_sta {
private Singleton_sta() {
}
static class Factory {
private static Singleton_sta object = new Singleton_sta();
}
//调用该方法的时候静态内部类才会被加载
private static Singleton_sta getSingleton() {
return Factory.object;
}
}
对象初始化的时机不是Singleton被加载的时候,而是静态方法getSingleton被调用时,使用classLoader的加载机制完成单例创建的懒加载。
补充说明一下类的初始化过程:
- 使用classLoader加载类:查找并加载类的二进制数据
- 连接:
验证:确保被加载类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把类中的符号引用转换成直接引用
其中准备和解析的顺序不一定 - 初始化
类初始化的时机:
- 遇到new、getStatic、pubstatic、或invokestatic这4条指令时。(被final修饰、已在编译期把结果放入常量池的静态字段除外)
- 使用反射包的方法对类发生调用时
- 如果初始化一个类的时候,如果发现父类还没有初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。有且只有以上这四中情况才会触发初始化