模式定义;保证一个类只有一个实例,并且提供一个全局访问点。
应用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池。就是被复用的。。
懒汉模式,饿汉模式,静态内部类,反射攻击实例,枚举,序列化
懒汉:延迟加载,
public class LazySingletonTest {
public static void main(String[] args) {
//单线程
// LazySingleton instance=LazySingleton.getInstance();
// LazySingleton instance1=LazySingleton.getInstance();
// System.out.println(instance==instance1);
//两个线程
new Thread(()->{
LazySingleton instance=LazySingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
new Thread(()->{
LazySingleton instance=LazySingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
}
/**instance = danli.LazySingleton@437d73bc
instance = danli.LazySingleton@2ea7cb0b
这是加synchronize之前,加上之后,只会出现相同的结果*/
}
class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton(){
}
//提供全局的访问点
//当调用instance的时候,才会实例化
public synchronized static LazySingleton getInstance() {
if (instance == null){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance=new LazySingleton();
}
return instance;
}
}
加锁的作用,就i是为了保证只new 一个实例,但是加syn就是不管有没有初始化都会加锁,但是当instance!=null的时候
是不需要加锁的,直接返回
if (instance == null){
//但是当两个线程都没有实例的时候,就会都进去实例化,
// 所以就是两个线程都会实例化,所以要再判断一下
synchronized (LazySingleton.class){
if (instance == null) {
instance=new LazySingleton();
}
}
}
return instance;
new 会在堆空间,开辟一块空间。CPU会有即时编码,有指令重拍。1、分配空间2、初始化3、引用赋值。2,3可以倒,但是不影响运行
volatile会保证Java命令顺序执行,不会出现指令重排现象。
private volatile static LazySingleton instance;
延迟加载,在使用的时候,进行加载。要注意的情况:
1)多线程情况下,线程安全问题,可以syn解决一些
2)double check 加锁进行优化
3)还要防止Java的即时编码(JIT),CPU有可能出现的指令重排,导致使用到没有初始化的实例,加volatile关键字。
饿汉模式---
public class HungrySingletonTest {
public static void main(String[] args) {
//new HungrySingleton();这样不能保证单例
HungrySingleton instance=HungrySingleton.getInstance();
HungrySingleton instance1=HungrySingleton.getInstance();
System.out.println(instance==instance1);
}
}
//饿汉模式
// 主要是通过类加载机制,
/**1、加载二进制数据到内存,生成对应的class数据结构
* 2、连接:a.验证b.准备(给类的静态成员变量赋默认值)c.解析
* 3、初始化:给类的静态变量赋初值
* 在类加载的时候,是在类被调用的时候,*/
class HungrySingleton{
private static HungrySingleton instance=new HungrySingleton();
//私有构造函数,保证在外部不能实例化,只要是为了保证单例
private HungrySingleton() {
}
//提供公开的方法,以便访问
public static HungrySingleton getInstance(){
return instance;
}
}
静态内部类:
public class InnerClassSingletonTest {
public static void main(String[] args) {
// InnerClassSingleton instance=InnerClassSingleton.getInstance();
// InnerClassSingleton instance1=InnerClassSingleton.getInstance();
// System.out.println(instance==instance1);
//多线程
new Thread(()->{
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
new Thread(()->{
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println("instance = " + instance);
}).start();
}
}
class InnerClassSingleton{
private static class InnerClassHolder {
/**在静态内部类进行属性的初始化,也算是懒加载,不使用的时候,不会实例化*/
private static InnerClassSingleton instance=new InnerClassSingleton();
}
/**提供私有的构造函数,就能保证别人不能再外部访问*/
private InnerClassSingleton(){
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
原理:
1)本质上是利用类的加载机制来保证线程安全
2)只有在实际中使用的时候,才会触发类的初始化,所以也是一种懒加载。
反射攻击实例:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//反射,,,静态,还有懒汉模式
//这是拿到构造函数
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
//这里是拿到访问权,就算是private修饰也可以取到
declaredConstructor.setAccessible(true);
//然后进行实例化
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
InnerClassSingleton instance = InnerClassSingleton.getInstance();
//这里的结果是false。
System.out.println(instance==innerClassSingleton);
}
}
静态和饿汉都可以被反射进行攻击,所以要加防护,,,,懒汉不能保证
/**提供私有的构造函数,就能保证别人不能再外部访问*/
private InnerClassSingleton(){
if (InnerClassHolder.instance!=null){
throw new RuntimeException("报错,instance已经初始化,单例不允许多个实例");
}
}
保证其他地方不能用反射的方式进行多例的实例化。
枚举举例:
1)天然不支持反射创建对应的实例,且有自己的反序列化机制
2)利用类加载机制保证线程安全
public enum EnumSingleton {
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
}
class EnumTest{
public static void main(String[] args) {
EnumSingleton instance=EnumSingleton.INSTANCE;
EnumSingleton instance1=EnumSingleton.INSTANCE;
//结果为true
System.out.println(instance== instance1);
}
}
//可以设置参数,一个string类型,一个int类型
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
//这里进行访问权设置,private修饰的都可以
declaredConstructor.setAccessible(true);
//为什么设置这两个参数,底层要继承Enum抽象类(里面的参数为name,int),
EnumSingleton instance = declaredConstructor.newInstance("INSTANCE", 0);
这个方法报错,不支持enum类型的对象创建。所以用enum是可以进行单例的创建,不会被反射使用,也是线程安全的
序列化:
implement Serializable,,
当用流进行时,writeOutFile和ObijectInputStream得到的结果并不是一样的。可以加版本号,然后就可以控制内容保持一致。