唠嗑
居然把这个多线程的典型案例给忘记写博客了~,此时的心情和可达鸭一样一脸懵逼哈哈哈,写博客是件不容易的事情啊,一篇就得半天,还欠着网络部分好几篇呢。慢慢来把,贪多嚼不烂
首先我们得知道单例模式是一种设计模式~
写代码时有些常见场景,设计模式就是针对这些常见场景给出得一些经典解决方案~
单例模式的两种典型实现(Singleton~~singleDog
):
1.饿汉模式
举个洗碗的例子吧:中午这顿饭使用了四个碗,吃完之后,立即把这四个碗给洗了~~【饿汉】=》着急
饿汉的单例模式,是比较着急的去创建实例的。
2.懒汉模式
同上:中午这顿饭使用了四个碗,吃完之后,先不洗~晚上这顿只需要两个碗,然后只洗2个即可【懒汉】=》在计算机中一般都是褒义词(提高效率)
懒汉的单例模式,是不太着急去创建实例的,只是在用的时候,才真正的去创建~~
public class Singleton {
//1.使用static创建一个实例,并且立即进行实例化。
//这个instance对应的实例,应该是该类的唯一实例。
private static Singleton instance=new Singleton();
//2为了防止程序猿在其他地方不小心new 这个Singleton,就可以把构造方法设为private
private Singleton(){};
//3提供一个方法,让外面能够拿到唯一实例
public static Singleton getInstance(){
return instance;
}
}
class Singleton2{
//1.这里不是立即就初始化实例
private static Singleton2 instance=null;
//2.把构造方法设为private
private Singleton2(){};
//3.提供一个方法来获取到上述单例的实例
//只有当真正需要这个实例的时候,才会真正的去创建这个实例
public static Singleton2 getInstance(){
if(instance==null){
instance=new Singleton2();
}
return instance;
}
}
当前虽然加锁以后,线程安全问题得到了解决,但是呢又存在了新的问题~~
对于刚才这个懒汉模式的代码来说,线程不安全是发生在
instance
被初始化之前的,未初始化的时候,多线程调用getinstance
,就可能同时涉及到读和修改。
但是一旦
instance
被初始化之后(一定不是null
,if的条件一定不成立了),getinstance
操作就只剩下两个读操作~~也就线程安全了
而按照上述的加锁方式,无论代码是初始化之后,还是初始化之前,每次调用
getinstance
都会进行加锁,也意味着即使是初始化之后(线程已经安全了),但是仍然存在大量的锁竞争。
改进方案:
让getinstance
,初始化之前,才进行加锁,初始化之后,就不再进行加锁操作了(在加锁这里加上一层条件判定即可,条件就是当前是否已经初始化完成~~instance==null
)
代码如下:
//如果这个条件成立,说明当前的单例未初始化过,存在线程安全的风险,就需要加锁
if (instance==null)
{
synchronized (Singleton2.class){
if(instance==null){
instance=new Singleton2();
}
}
}
return instance;
ps:
可能有些小伙伴会在想怎么这两个if条件一模一样,其实这只是一个美丽的巧合而已~~这两条件起到的预期目的是完全不一样的,上面的条件判定是是否要加锁,下面的条件判定的是是否要创建实例,只是碰巧这两个目的都是判定instance
是否为null
。
我们来看看现在的完整代码:
class Singleton2{
//1.这里不是立即就初始化实例
private static Singleton2 instance=null;
//2.把构造方法设为private
private Singleton2(){};
//3.提供一个方法来获取到上述单例的实例
//只有当真正需要这个实例的时候,才会真正的去创建这个实例
public static Singleton2 getInstance(){
//如果这个条件成立,说明当前的单例未初始化过,存在线程安全的风险,就需要加锁
if (instance==null)
{
synchronized (Singleton2.class){
if(instance==null){
instance=new Singleton2();
}
}
}
return instance;
}
}
class Singleton2{
//1.这里不是立即就初始化实例
private static volatile Singleton2 instance=null;
//2.把构造方法设为private
private Singleton2(){};
//3.提供一个方法来获取到上述单例的实例
//只有当真正需要这个实例的时候,才会真正的去创建这个实例
public static Singleton2 getInstance(){
//如果这个条件成立,说明当前的单例未初始化过,存在线程安全的风险,就需要加锁
if (instance==null)
{
synchronized (Singleton2.class){
if(instance==null){
instance=new Singleton2();
}
}
}
return instance;
}
}
这个代码是完全体的线程安全单例模式:
1)正确的位置加锁
2)双重if判定
3)volatile
小拓展:
static
表示的意思和字面这个单词,没有任何的联系(历史遗留问题)
追溯到C
语言,即使是C
语言中,static
的效果也是不和字面意思一样
其实在上古时期,那时候的static
是表示把变量放到“静态内存区”,于是就引入了关键字static
随着计算机的发展,静态内存区这个东西逐渐没了~~但是static
关键字仍然在,并且被赋予了其他的功能,C++
的static
除了上述C中的功能之外,又有了新的用法,修饰一个类的成员变量和成员函数,此处static修饰的成员就表示类的成员,
Java
把C++
的static
给继承过来了
为啥后来把static
给保留下来并且成为表示类属性,而不用其他的词呢?
答:一个编程语言,要想新增加关键字,是一件非常有风险的事情。