单例模式:是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点
怎么实现?
- 隐藏其所有的构造方法(私有化构造方法)
- 提供一个全局访问点
场景:
重量级的对象,不需要多个实例,如线程池(希望我们的线程池是可以复用的,不希望每次都去创建一个线程池),数据库连接池(我们希望我们每次从池子里直接拿连接,而不是每次请求都去创建一个连接池)
1)懒汉式:延迟加载,正如它的名字一样,他是比较懒的,只有别人用我的时候,我才去创建
//单例模式,在别人要调用的时候才去创建
public class Singleton {
//私有化构造方法,防止别人去实例化这个类
private Singleton() {};
//定义一个全局变量
private static Singleton single=null;
//利用静态工厂方法创建实例
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
测试类:经过测试,看到创建的这两个对象是同一个
public class Test {
public static void main(String[] args) {
//Singleton s=new Singleton();报错,因为构造方法私有化,不允许我们去实例化他
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);//true
}
}
我们看到Singleton的唯一实例只能通过getInstance方法访问
懒汉式的单例模式是不安全的,因为当你去改变s1的东西的时候,你会发现s2的也会随之改变,因为他两本来就是一个东西,这对于多线程来说显然是不行的
懒汉式:不安全的实例
public class Singleton {
private int age=10;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private Singleton() {};
private static Singleton single=null;
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
测试:我们发现只是改变了s1的变量值,但是s2的变量值也随之改变了,这就是单例模式不可取的地方
public class Test {
public static void main(String[] args) {
Singleton s1=Singleton.getInstance();
s1.setAge(20);//改变s1的变量值
Singleton s2=Singleton.getInstance();
System.out.println(s2.getAge());//结果20
}
}
并且,我们都知道,即使一个构造方法是私有的。我们也可以通过反射来实例化这个类
例:利用反射破坏单例模式
public class Singleton {
private int age=10;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private Singleton() {};
private static Singleton single=null;
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
System.out.println("创建了一个单例实例");
}
return single;
}
}
测试类
public class Test {
public static void main(String[] args) throws Exception {
Class clazz=Singleton.class;
//getDeclaredConstructor方法是通过反射获取到一个类的私有的构造方法,不传参表示得到的是无参的构造函数
Constructor<Singleton> sConstru=clazz.getDeclaredConstructor();
//setAccessible设置值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查
sConstru.setAccessible(true);
Singleton s=sConstru.newInstance();
s.getInstance();
}
}
输出结果:代表通过反射创建了这个类的实例,即使这个类的构造方法是私有化的
创建了一个单例实例
懒汉模式的缺点:
- 线程安全的问题
- double check 加锁优化
- 编译器(JIT),CPU有可能对指令进行重新排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰,volatile修饰的字段,可以防止指令重排
安全的懒汉式:
- 如果将锁加在方法上,那么不管我们这个实例有没有初始化,我们都会进行一次加锁,我们真正想要的是只有在已经初始化的情况下再去创建这个实例的话需要加锁来一个判断
- 所以我们可以对我们这个锁来一个延迟,如果下例中的s有值,在判断不为空的情况下,我们是没有必要加锁的,就直接返回了,因此我们就可以在它等于空的时候,再对它进行加锁
public class LazySingleton{
private LazySingleton(){}
private static LazySingleton s=null;
public static LazySinglrton getInstance(){
//如果这个实例为空的话,对它加锁
if(s==null){
//加到这还是有问题的,如果有两个程序同时都走进来了,就是我们还没有实例化的时候,发生的一个并发的场景,比如说有T1,T2两个线程,T1已经实例化完成了return,T2此时刚好执行到了注释为8888这一行,然后也实例化,等于T1实例化了一次,T2也实例化了一次
synchronized(LazySingleton.class){//8888
//基于上面的问题,因此我们还需要再判断一次是否已经实例化过了
if(s==null){
return new LazySingleton();
//字节码层
//JIT
//1.分配空间
//2.初始化
//3.引用赋值
}
}
}
return s;
}
}
2)饿汉式:表示你没有和我要东西,但是我早已经准备好了
在 类进行初始化的时候就创建了类对象,本质上就是借助 jvm的类加载机制,保证了实例的唯一性
public class HungrySingleton {
private HungrySingleton() {};
//在类加载的时候就初始化了
private final static HungrySingleton single=new HungrySingleton();
public static HungrySingleton getInstance() {
return single;
}
}
类加载过程:
- 加载二进制数据到内存中,生成对应的Class数据结构
- 连接:a.验证 b.准备(给类的静态成员变量赋默认值),c.解析
- 初始化:给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化(当前类时启动类即main函数所在的类,直接进行new操作,访问静态属性,访问静态方法,用反射访问类,初始化一个类的子类等)
3)静态内部类的方式:
- 本质上是利用类的加载机制来保证线程的安全
- 只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式
public class InnerStaticSingleton {
public static class Proxy{
private static InnerStaticSingleton instance=new InnerStaticSingleton();
}
private InnerStaticSingleton(){}
public static InnerStaticSingleton getinstance() {
return Proxy.instance;
}
}
工厂模式
先到这,接着学的时候更