不管是设计模式也好,别的模式也要,他都是为了解决问题而发明的有效的方法。除了我们已经熟悉的23种设计模式以外,还有MVVM、Combinator等其它的东西,都已经是前辈们经过多年的摸爬滚打总结出来的,其有效性不容置疑。我这篇文章也不会用来证明设计模式是有用的,因为在我看来,这就跟1+1=2一样明显(在黑板上写下1+1=2)
而这,在现在这个追求高质量代码的时代,虽然显得有一些复杂,但是我个人还是“推崇”(看好了,我有引号的)这个东西,毕竟面试必问系列,你咋整
来看今天的内容吧,有代码,有实例,并且有一些内容我直接放在代码中通过注释进行讲解,会更好理解
文章首发公众号:Java架构师联盟,每日更新技术好文
一、设计部分:单例的实现思想、代码及注意问题
package com.test.hibernate;
/*生成一个懒汉式单例的基础理解:
1.Singleton顾名思义就是只能创建一个实例对象。。所以不能拥有public的构造方法
2.既然构造方法是私有的,那么从外面不可能创建Singleton实例了。。只能从内部创建。。所以需要一个方法来创建此实例,也因此只能通过类名来创建对象。。此方法肯定必须是static的
3.静态的getInstance方法要返回一个Singleton实例。。就要一个Singleton类型的变量来存储。。声明一个Singleton类型的属性。。同样需要是static 的。。静态方法只能访问静态属性。。。
!!!前3步是单例的共性!后三步是懒汉式需要考虑的地方!
4.为了保证只生成一个实例,需要做判断是否为null
5.此时考虑线程问题,假设有两个线程。。thread1,thread2。。thread1运行到判断那个Singleton类型的变量是否为null,然后跳到了thread2。。也运行到判断之后。。。此时两线程都得到single为空。。。那么就会有两个实例了。。。解决办法。。同步
6.同步又要考虑效率,不能有太多的没用同步
* */
//思考总结:其实饿汉式没什么问题,问题就是出现在懒汉式上,一般就是牵扯到执行效率和线程安全2个角度上来思考
//个人感觉 如果是饿汉式就用天然的没毛病,如果想用懒汉式就用静态内部类方式吧
//存在问题:如何在2个jvm上保证单例还未解决:这个就牵扯到分布式锁,可以用zookeeper来实现
//还缺少一种懒汉式的枚举方式实现有待研究,听说这个方法也不错。
public class danli { //模拟一下静态代码块的使用方式,静态代码块在类加载的运行,先静态代码块》再构造代码块》再构造函数 ,只研究单例可以忽略
public static final String STR1;
static {
STR1 = new String("zzh");
}
}
//下面正式演示各种单例的实现:
class danli2{//单例饿汉式(非延时加载),提前加载,有利于速度和反应时间,天然的线程安全的。没毛病
private danli2(){};
private static final danli2 two = new danli2();//final可加可不加,final的目的就是最终的,只允许一次赋值,但不加是因为没法在本类外给他赋值了,因为构造方法是私有的没法创建这个类的对象了,而且这个成员变量也是私有的所以不能在外面调用到,但是可以在本类中的其他方法调用到,所以其实还是可以修改的,所以还是加上final吧
public static danli2 getSingleInstance(){
return two;
}
}
class danli3{ //单例懒汉式(延时加载),用的时候再去加载,有利于资源充分利用
private danli3(){};
private static danli3 three = null;
public static synchronized danli3 getSingleInstance(){//加上synchronized变得线程安全了,但是效率下降了,每次还需要检查同步等等
if(three == null){//保证只生成一个实例
three = new danli3();
}
return three;
}
}
/* 该类跟上面那个是一样的,上面是synchronized方法,下面这个是代码块。
class Singleton {
private Singleton() {}
private volatile static Singleton instance = null;
public static Singleton getInstance() {
synchronized (Singleton.class) {//利用synchronized代码块,每次需要先检查有没有同步锁,效率较低,为了解决这个问题又提出了加入双层检查,也就是在这个同步代码块的外面再加一层为null判断,来减少除第一次以外的同步检查,提高了效率
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
*/
双重检查加锁就是在同步代码块的外面一层再来一个== null的判断,解决除第一次以外所有的同步判断导致的效率下降问题
//但是这个双重检查加锁在多线程环境下存在系统崩溃的可能(一个线程初始化一半的对象,被第二个线程直接拿去用了,所以系统崩溃了)
/*原因如下
1、线程 1 进入 getInSingleton() 方法。
2、由于 uniqueInstance 为 null,线程 1 在 //1 处进入 synchronized 块。
3、线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。
4、线程 1 被线程 2 预占。
5、线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 uniqueInstance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。
6、线程 2 被线程 1 预占。
7、线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
*/
class Singleton {//双重检查加锁,线程相对安全了,避开了过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了),效率比上面那个能提高一些
// volatile关键字确保当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量,这个关键字其实也解决了上面说的系统可能崩溃的问题,因为使用这个变量也需要一个线程一个线程的来使用了
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInSingleton() {
if (uniqueInstance == null) {// 检查实例,如是不存在就进行同步代码区
synchronized (Singleton.class) {//1 // 对其进行锁,防止两个线程同时进入同步代码区
if (uniqueInstance == null) {//2 // 双重检查,非常重要,如果两个同时访问的线程,当第一线程访问完同步代码区后,生成一个实例;当第二个已进入getInstance方法等待的线程进入同步代码区时,也会产生一个新的实例
uniqueInstance = new Singleton();//3
}
}
}
return uniqueInstance;
}
// ...Remainder omitted
}
//使用静态内部类是没问题的,而且效率也不会降低,而且还是懒加载
class Singleton2 {//jvm加载SingletonHolder的时候会初始化INSTANCE,所以既是lazy的又保证是单例的
private static class SingletonHolder {//静态内部类,只会被加载一次(在加载外部类的时候),所以线程安全,注意静态只能使用静态
static final Singleton2 INSTANCE = new Singleton2();
}
private Singleton2 (){}//静态构造方法
public static Singleton2 getInstance() {//对外提供单例的接口
return SingletonHolder.INSTANCE;
}
}
class ceshi{//只是简单测试了一下单例,都为true,可以忽略
public static void main(String[] args) {
System.out.println(danli.STR1 == danli.STR1);//true
System.out.println(danli2.getSingleInstance() == danli2.getSingleInstance());
.........