单例模式
只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
实现:
- 饿汉模式
public class Hungry {
private Hungry() {}
private static Hungry instance = new Hungry(); // 只会有这么一个对象
public static Hungry getInstance() {
return instance;
}
}
很早的时候就把类的对象创建出来了,所以为饿汉。
性能不高:因为不管外部有没有调用它,它始终创建出来了实例对象。
- 懒汉模式
public class Lazy {
private static volatile Lazy instance = null;
public static Lazy getInstance() {
if (instance == null) {
//1
synchronized (Lazy4.class) {
if (instance == null) {
instance = new Lazy();
}
}
}
return instance;
}
}
为什么这样写性能高呢?原因:
- 1.instance初始化为null,只有被调用时才会进行初始化,并且只初始化一次(被初始化后就不为null了)
- 2.为什么要用synchronized呢?当然是为了保证线程安全,java中先判断后赋值的操作时典型的线程不安全操作,必须要加锁保护。
- 3.为什么要二次判断(if (instance == null))?
- 外部的判断:如果没有外部这一判断的话,每一次线程调用这个方法的话都会产生锁的竞争,这是耗费性能的。而加这一层判断的话线程在锁的竞争上只会产生一次竞争,也就是第一次,其余时间已经完成了类的实例化,instance不为null了,所以也就不会在竞争了。
- 内部判断:我们假设一个线程经过外部的第一次判断后走到了代码中的 //1 位置,若此时cpu发生了调度,而另一个线程也经过外部判断后抢到了锁而进行了实例化操作,那么这时候instance是不为空的;此时假设CPU又调度刚才走到 //1 位置的那个线程继续执行拿到锁,此时不加内部的判断的话就又会执行实例化操作,但是此时类已经被实例化了,所以就要加上内部的判断。
- 4.为什么要加volatile?
首先,我们了解下代码重排序:
- JVM和操作系统在执行命令时为了提高效率可能会把执行一条语句的多个指令打乱顺序;
- 比如:
instance = new Lazy();
- 按照我们写的顺序应该为:1.构造一个初始化内存空间——>2.对类进行实例化——>3.将这个实例化对象的引用给instance
- 而重排序后顺序可能为这样:1.构造一个初始化内存空间——>2.将这个实例化对象的引用给instance——>3.对类进行实例化
这样就会产生问题,如下:
- 此时instance虽然不为null,但是并没有完成类的实例化,所以其他线程不会在执行
instance = new Lazy();
,但是使用instance的话就会发生错误。- 加volatile可以防止代码重排序,也就解决了上述问题。
对你有帮助的话还请一键三连。