何为单例
概念:某个类中只存在一个实例,且只提供一个全局访问点
场景:很多时候一些类不需要重复创建,比如配置、工具类等
与静态方法区分:静态方法也符合上述描述,某个类不需要重复创建。他和单例模式性能相差不大。其最主要的区别就是面向对象思想的区分,静态方法没有继承等概念。所以如果一个方法如果与实例无关,那么他就是静态的。
单例区分
单例分为懒汉和饿汉。
懒汉只有在使用的时候才会生成对象,所以效率更好一点。
但利弊总是共存的,在使用懒汉的时候就要保证线程安全。
最简单的懒汉:
public class Test {
private Test() {
}
private Integer age;
private static Test instance;
public static Test getInstance() {
if (instance == null) {
instance = new Test();
}
return instance;
}
}
获取实例的时候先判断是否存在,如果不存在那么实例化一个单例。
问题
但是如果多线程访问此方法就会有问题
多线程会出现多个实例
所以为了解决线程不安全的问题可以有两种解决方法
1、直接在方法上加锁
2、使用双重校验锁
解决方案
方案一:方法加锁
直接在获取实例方法上面加锁
public synchronized static Test getInstance() {
if (instance == null) {
instance = new Test();
}
return instance;
}
缺点:每次获取实例都要加锁,效率不行
方案二:双重校验锁
仅在第一次初始化的时候会加锁,存在实例就不会加锁了
public static Test getInstance() {
if (instance == null) {
synchronized (Test.class) {
if (instance == null) {
instance = new Test();
}
}
}
return instance;
}
多线程第一次访问的时候只有一个线程会获得锁,其他线程阻塞。
第一个线程创建完对象释放锁后其他线程进入锁,又进行判断,此时instance已经不是null所以其他线程不会再创建实例
图解双重校验锁
1、首次创建有三个线程同步进入
第一个线程获得胜利创建实例,其他线程进入锁后,发现已经创建了实例,所以无奈走了
2、非首次创建
由于instance不为null,所以后面的线程连锁都见不到