应用场景
代码中有的概念,不应该存在多个实例,此时应该使用单例模式。(JDBC中在DataSource这样的类中,在一个程序中只有一个实例,不应该实例化多个DataSiurce对象)
实现
保证指定的类只能有一个实例(如果有创建多个实例就会报错)
饿汉模式
饿汉模式,”饿“是值只要类被加载,实例就会立刻创建(实例创建时机比较早)
public class ThreadDemo {
//创建类
//饿汉模式
static class Singleton{
//把构造方法、变成私有,此时在此类外面就无法new这个类的实例
//饿汉模式,”饿“是值只要类被加载,实例就会立刻创建(实例创建时机比较早)
private Singleton(){
//再创建一个static成员,表示Singleton类的唯一实例
//static 和类相关,和实例无关,类在内存中只有一份,static成员也只有一份
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
public static void main(String[] args) {
//getuinstance()是获取实例的唯一方式
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
}
}
}
结果:true,两个对象获得的是同一个对象
懒汉模式
一般认为,懒汉模式比饿汉模式效率更高,懒汉模式有很大的可能是“实例用不到”,此时就节省了实例化的开销。
public class TheadDemo {
//使用懒汉模式进行实现Singleton 类被加载的时候,不会立刻实例化
//等到第一次使用这个实例的时候再进行实例化
static class Singleton{
private Singleton(){}
private static Singleton instance = null; //类加载的时候没有实例化
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();//第一次调用getInstance时才能实例化
}//如果代码一整场都没有调用getInstance,此时实例化的过程也就省略了
return instance;
}
}
}
三次优化(使线程安全)
1、加锁
要使判断和修改保持原子性,
上面两个方法相当于接孩子放学,一个在教室门口等,一个在学校门口等。
锁中包含的代码越多,粒度越大,锁的粒度越小越好,粒度越大,说明代码的原子操作就多了,很多本来线程安全的代码没必要再去保证他的安全
2、只要在第一次的实例化之前调用的时候加锁
懒汉模式在实例化之前是存在线程不安全的,但是如果在实例化之后,后面再去并发执行调用getInstance就是线程安全的,在上面的代码中,即使实例已经创建好了,每次调用getInstance还会涉及加锁解锁,而在这里的加锁解锁其实已经不必要了
所以只要在第一次的实例化之前调用的时候加锁,后面不加锁。
3、目前加锁之后,会存在内存可见性问题
当一个线程被加锁修改后,另外一个线程读取的时候,存在两次读取,只有第一次读取才从内存中读取,后面的是从CPU中读取寄存器(上次读到的结果)
最终优化,在instance之前加上volatile
public class TheadDemo {
//使用懒汉模式进行实现Singleton 类被加载的时候,不会立刻实例化
//等到第一次使用这个实例的时候再进行实例化
static class Singleton {
private Singleton() {
}
private volatile static Singleton instance = null; //类加载的时候没有实例化
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();//第一次调用getInstance时才能实例化
}//如果代码一整场都没有调用getInstance,此时实例化的过程也就省略了
}
}
return instance;
}
}
}
单例模式和“线程”关系
两种单例模式的实现方式是否是线程安全的
饿汉是线程安全
类加载只有一次机会,不可能并发执行,多线程同时调用getInstance,由于getInstance只做了一件事,就是读取instance实例的地址=》多个线程在同时读取同一个变量,没有修改。
饿汉模式不涉及多线程同时修改同一个变量,所以是安全的。
懒汉是线程不安全
多线程同时调用getInstance,由于getInstance做了四件事:
1、就是读取instance的内容
2、判断instance是否为null
3、如果instance为null,就会new实例,会修改instance的值
4、返回实例地址
懒汉模式在实例化之前是存在线程不安全的,但是如果在实例化之后,后面再去并发执行调用getInstance就是线程安全的。
线程不安全
1、线程的调度是抢占式执行
2、修改操作不是原子的
3、多线程同时修改同一个变量
4、内存可见性
5、指令重排序