4、单例模式
单例模式就是确保某个类只有一个实例,有三种实现方式:饿汉模式、懒汉模式、静态内部类。(枚举类也是实现单例)
单例有三个特点:1、私有构造函数;2、有指向自己实例的私有静态引用;以自己实例为返回值的静态公有方法。
1、饿汉模式
就是在类加载初始化的时候就主动创建实例。
优点:线程安全,调用效率高。
缺点:在还没被使用前一直占用内存,浪费内存空间。
//饿汉模式
public class Single {
private Single(){ //注意是私有
}
private static Single single = new Single(); //私有且静态
public static Single newInstance(){ //公有静态
return single;
}
}
class Text{
public static void main(String[] args) {
Single single1 = Single.newInstance();
Single single2 = Single.newInstance();
System.out.println(single1);
System.out.println(single2);
}
}
2、懒汉模式
在类初始化时不创建实例,而是要在真正使用时再创建
2.1、懒汉模式的DCL单例,线程不安全
//懒汉模式,DCL单例,线程不安全(因为变量没加volatile)
public class LazySingle {
private LazySingle(){
}
private static LazySingle lazySingle = null;
public static LazySingle newInstance(){
//双重检测锁
if(lazySingle==null){
synchronized (LazySingle.class){
if(lazySingle==null){
lazySingle = new LazySingle();
/**
* new有三个步骤:1、分配内存空间给对象;2、执行构造函数初始化对象;3、为对象赋给引用
* 但是由于变量没加volatile,所以会有重排序变成:1->3->2
* 那么当线程A执行到完第3步骤(lazySingle引用不为null了),执行第2步骤时线程切换,
* 线程B开始执行newInstance方法,判断lazySingle的引用不为null,直接return lazySingle,
* 那么返回将是一个未执行初始化的对象,所以现在也是线程不安全。
*/
}
}
}
return lazySingle;
}
}
2.2、DCL单例的完善:
为静态引用添加volatile,则线程安全。
private static volatile LazySingle lazySingle = null;
3、静态内部类
通过静态内部类来实现单例
public class InnerSingle {
private InnerSingle(){
}
public static InnerSingle newInstance(){
return InnerClass.innerSingle;
}
private static class InnerClass{
private static InnerSingle innerSingle = new InnerSingle();
}
}
**反射破除以上单例:**其实上面的单例可以用反射机制来进行破除(就是能创建多个实例,而不是单个),以懒汉模式为例子:
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazySingle> constructor = LazySingle.class.getDeclaredConstructor();
constructor.setAccessible(true); //这个方法设置为true,运行访问私有变量及私有函数。
LazySingle lazySingle1 = constructor.newInstance();
LazySingle lazySingle2 = constructor.newInstance();
System.out.println(lazySingle1); //single.LazySingle@4554617c
System.out.println(lazySingle2); //single.LazySingle@74a14482
}
}
通过反射获得了多个单例,那么如何真正获得单例对象呢?点进constructor.newInstance看一下:
通过上述代码,我们知道了枚举类才是真正的单例!!!
4、通过枚举实现单例
public enum EnumSingle {
INSTANCE("我是单例的",5);
String name;
int i;
EnumSingle(String name,int i){
this.name = name;
this.i = i;
}
}
class Test3{
public static void main(String[] args) {
EnumSingle single1 = EnumSingle.INSTANCE;
EnumSingle single2 = EnumSingle.INSTANCE;
System.out.println(single1==single2); //true
System.out.println(single1.name); //输出:我是单例的
System.out.println(single1.i); //输出:5
}
}
通过反射创建枚举类将会报错,代码如下:
class Test3{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
constructor.newInstance("你好",5); //报错
}
}
报的错是NoSuchMethodException,说明构造函数不是这个,在底层枚举类的构造函数被更改,需要使用专业软件反编译才能知道。