1. 基础介绍
- 核心作用:保证一个类只有一个实例,并提供一个访问该实例的全局访问点;
- 优点:由于单例模式只生成一个实例,所以减少了系统的开销,当一个系统启动需要较多的资源时,可以直接在系统启动时产生一个单例对象,然后使其永久驻留内存;单例模式可以在系统设置全局访问点,优化共享资源的访问;
- 两种主要的单例模式实现方式:饿汉式,懒汉式;
2. 饿汉式介绍
- 线程安全,无法延时加载,也就是只要系统一运行,这个单例模式的类对象就会被加载到内存;
- 简单步骤:
首先,将构造器私有化,这样就无法在类的外部直接创建对象;
然后再类中创建一个本类对象(使用private static修饰,做到全局使用);
再定义一个getInstance()方法,用来在其它的类中创建单例对象;public class Singleton { //通过这两个修饰的对象,是线程安全的; private static Singleton singleton = new Singleton(); //创建一个私有的构造方法; private Singleton() { } //定义一个static方法用来在其它类中获得这个类的实例; public static Singleton getInstance() { return singleton; } }
3. 懒汉式
- 线程安全,延时加载,真正使用时创建;调用的效率比较低(因为每一次调用,都会重新创建一个对象);
- 简单步骤:
首先先创建一个空的类实例,使用private static修饰;
然后创建一个私有的构造方法;
最后定义一个加锁的getInstance()方法,用于返回该类的对象;public class SingletonDemo { //创建一个该类的空对象引用; private static SingletonDemo sing; //创建该类的私有构造方法; private SingletonDemo(){} //创建一个getInstance()方法,用于创建该类的对象;这里用个锁,就可以防止创建多个对象同时被创建;当然,锁也可以放到里面去,放外面可能效率更加低; public synchronized static SingletonDemo getInstance(){ //判断sing对象是否为空,为空则创建;反之,直接输出; if(sing==null){ sing = new SingletonDemo(); } return sing; } }
4. 双重检测锁实现
- 上面讲到,可以将synchronized放入到方法里面去,这里就是这样做的,这样可以提高它锁的效率;但是涉及很到jvm底层,目前还没学习,暂做了解;
大致的步骤与上面一致,我就在懒汉式中直接修改了;public class SingletonDemo { //创建一个该类的空对像; private static SingletonDemo sing=null; //创建该类的私有构造方法; private SingletonDemo(){} //创建一个getInstance()方法,用于创建该类的对象; public static SingletonDemo getInstance(){ //判断sing对象是否为空,为空则创建;反之,直接输出; if(sing==null){ //在里面进行加锁 synchronized (SingletonDemo.class) { if(sing==null) sing = new SingletonDemo(); } } return sing; } }
5. 登记式/静态内部类实现方式
- 外部没有static属性,不会像饿汉式一样直接加载对象;只有调用getInstance()方法时加载对象,加载类时,线程安全,因为创建的外部类的对象是static final修饰,导致只有一个实例,只能赋值一次;
- 有着饿汉式的高效调用,懒汉式的延时加载;
基本结构介绍:
首先先定义一个static的静态内部类,在该类中创建一个外部类的对象,并用private static final修饰该对象;
创建一个私有化的外部类构造方法;
最后在外部类中创建一个getInstance方法,用于返回该外部类的对象;public class SingleStatic { //创建一个私有的属性; private String name = "神"; //先创建一个该类的私有构造方法; private SingleStatic() { } //创建一个静态的内部类; private static class SingleInside { //定义一个外部类的实例;在内部类中是可以调用外部类的私有构造器的; private static final SingleStatic sig = new SingleStatic(); //我想看看能不能调用外部类的私有属性,可以,很好, public static String string(){ String name=sig.name; return name; } } //创建一个方法返回该类的对象; public static SingleStatic getInstance() { return SingleInside.sig; } public static String getname(){ return SingleInside.string(); } }
测试类
class test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
SingleStatic aStatic = SingleStatic.getInstance();
System.out.println(aStatic);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
SingleStatic instance = SingleStatic.getInstance();
System.out.println(instance);
}
}).start();
System.out.println(SingleStatic.getname());
}
}
结果输出(符合单例模式):
6. 枚举
- 通过枚举实现单例模式,枚举本身就是一种单例模式,无延时加载,线程安全,调用效率高;
public enum SingleEnum { //这就相当与创建好了; SINGLE_ENUM; }
7.总结
很明显,在单例模式中除了枚举的方式之外,其它的方式都可以通过反射的方式,强行获取对象的构造方法;当然也是可以避免的;只需要在创建前再次判断抛出一个异常结束就可以了;还有一些比如反序列化也可以进行破解,这里就不一一记录,感觉这种东西,只要了解,知道怎么规避就可以了,防止反序列化破解的话可以直接使用一个方法 readResolve() ,这个方法原理不是很懂,看别人的博客说是在使用对象流的时候会进行检查,如果有这个方法就会直接返回已有的对象,这个方法的出处没有,仿佛就是自己定义的一样;
Java杂记
a) 内部类可以直接调用外部类包括private的成员变量,构造方法;
b) 而外部类调用内部类需要建立内部类对象,当然,内部类也可以将方法设置为静态的;