目录
目录
一.设计模式
在软件开发中,常常会针对一些场景下遇到一些问题,对于这种问题场景,大佬程序员总结出了一些固定套路来解决这些问题,只要程序员采取了大佬们的套路来编写代码,即使程序员水平不够高,也可以很好地解决问题,降低了代码出现BUG的可能。
设计模式就像魔方的公式一样,我们只要按照公式,即使被扭的非常乱,很难复原,但只要按照公式操作,只要你耐心一点,就能实现复原。
二.单例模式
单例模式是设计模式之一,根据设计模式的名字可以看出它的特点,即在某个类在程序中只会存在唯一一个实例。在针对一些场景下,我们要求一个类只能存在一个实例。
而单例模式的具体实现,有饿汉模式和懒汉模式两种。
1.饿汉模式
该种单例模式会被称为饿汉模式的原因是实例一开始就会被创建,而不是等你需要获取的时候才创建,对这种略显急切的方式,称其更为形象的名字为饿汉。
代码:
class Singleton{
private static Singleton instance = new Singleton();
public Singleton getInstance(){
return instance;
}
private Singleton(){}
}
第一、instance被static修饰,这样一来,它就变成了类属性,在类加载时被创建,且只会创建一次,其次就是static保证了它一定会被创建,如果是普通成员,它被创建一定要是外部new这个对象的时候,但是Singleton这个类的构造方法是私有的,外部无法new,这就尬住了。
第二、Singleton这个类,提供了getInstance方法,以供外部获得instance实例。
第三、Singleton这个类,它的构造方法是被private修饰,所以外部是无法直接new的,这也保证了Singleton实例不会被多次创建。
2.懒汉模式
懒汉模式与饿汉模式很相似,它们实现单例的方式是一样的,差距就在懒汉模式为了对得起它的名字,将实例的创建放到了外部需要获取的时候。但懒汉这种行为是更加值得鼓励的,它只要需要使用的时候才创建,不会浪费资源。
上代码:
class SingleLazy{
private volatile static SingleLazy instance = null;
public SingleLazy getInstance(){
if(instance == null){
synchronized (SingleLazy.class){
if(instance == null){
instance = new SingleLazy();
}
}
}
return instance;
}
private SingleLazy(){}
}
可以看出,在外部没有调用getInstance之前,instance是为null的,在调用之后,才会调用构造方法,创建实例,这种你不要求它,它就永远不会行动的模式,用懒汉形容再合适不过。
对比饿汉与懒汉的实现,不同的地方主要就在与getInstance中的代码。那分析这段代码,我们就先屏蔽掉第一个if和synchronized,就看剩下的if。
public SingleLazy getInstance() {
if (instance == null) {
instance = new SingleLazy();
}
return instance;
}
如果代码这样些,看着多顺眼。但是奈何多线程的“特性”呢,这势必要考虑线程安全的问题。
为何加锁?
在多个线程调用getInstance的时候,如果一个线程做完if判断之后,就被CPU切走,另一个线程也做了if判断,两次判断都成立,那么就发生了两次new实例的现象,这就BUG了,所以我们必须要在每次做if判断之前加上锁,这样才能保证线程的安全。
如下图:
public SingleLazy getInstance() {
synchronized (SingleLazy.class) {
if (instance == null) {
instance = new SingleLazy();
}
return instance;
}
}
那么,锁也加了,线程安全问题也解决了,怎么还多了个if呢?
我们再think一下,这样的代码,线程每次调用getInstance都会上锁,然后判断,那如果instance已经被创建过了,但还是每次都会加锁,这就造成了不必要的开销,一个if判断语句的开销是大大小于加锁操作的。那么,在一开始先判断,就能避免加无意义的锁。
在这里,这两个if代码是一样的,但是它们的作用确是截然不同。第一个if是为了判断要不要加锁,当instance已经被创建后,就不会有线程安全问题了,加锁就无意义了,这个if能大大减少开销;第二个if是判断instance是否要被创建。
最后,instance还是被volatile修饰的,这里的具体原因也很简单:
每次在new新的对象的时候,会有三件事情的发生:
①分配一块内存空间;
②根据构造方法,在内存空间上构造出对象;
③把内存空间的地址赋值给instance引用。
由于编译器存在指令重排序的情况,即上述三个行为可能是123的顺序,也可能是132的顺序,虽然在单线程情况下指令重排序也不会发生什么影响,但是在多线程环境下,如果new 按照132的顺序来执行,第一个线程执行完3后,将地址赋值给instance引用,但是这个对象还没有被构造完全,那么线程被CPU切走后,第二个线程走if语句时,发现instance非null,就直接返回了一个残缺的对象,这就导致了程序的BUG。所以我们加上volatile关键字,这样就可以保证在new这个对象的时候不会发生指令重排序。
以上就是本文的全部内容了。