单例模式概念

 

当一个全局使用的类被频繁创建和销毁时,会大大降低运行效率,当您想要控制实例数目,节省系统资源的时候,这个时候就可以使用单例模式,那么什么是单例模式囊?

单例模式(Singleton Pattern) 是一种常用的软件设计模式。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

1、单例类只能有一个实例且必须是自己创建自己的唯一实例(单例模式操作的是同一个对象);

2、单例类必须给所有其他对象提供这一实例;

3、构造函数是私有的;

4、单例类中因为需要在外部使用类名.方法,因此需要将方法定义为静态的;相同原理,变量的定义也需要定义为静态的

饿汉模式

饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。

代码:

public class Student {
	String name;
	int age;
	String sex;
	// 饿汉模式:定义静态Student类型的变量,之后在别的类中使用操作的是同一对象。
	static Student student = new Student();

	private Student() {

	}

	public static Student getMethod() {
		return student;
	}
}

做一个简单地验证:验证是否是同一对象

public class Test {
public static void main(String[] args) {
	//调用类内静态方法。
	Student stu1 = Student.getMethod();
	Student stu2 = Student.getMethod();
	stu1.name = "刘一";
	System.out.println("学生1的名字是:"+stu1.name);
	stu2.name = "王二";//因为操作统一对象,这里将上面所赋值进行了覆盖。
	System.out.println("学生2的名字是:"+stu2.name);
	System.out.println("学生1的名字是:"+stu1.name);
  }
}

 结果:

学生1的名字是:刘一
学生2的名字是:王二
学生1的名字是:王二

从上面的测试中我们可以看出,操作的是同一个对象,在这里说明,单例模式特点就是操作同一个对象,在后面将不再进行验证。

那么饿汉模式的有哪些优缺点囊?

优点就是饿汉模式天生是线程安全的,使用时没有延迟。 缺点也比较突出,启动时即创建实例,启动慢,有可能造成资源浪费。

在实际开发中我们很多时候都希望对象可以尽可能地延迟加载,从而减小负载,这就需要另外一种模式:懒汉模式(Lazy load)

懒汉模式:

 

  懒汉模式的实现方式体现了延迟加载的思想:在一开始不加载资源或者数据,当要使用这个资源或者数据的时候,才去加载,

在实际开发中是一种很常见的思想,尽可能的节约资源。

       懒汉模式的实现方式也体现了缓存的思想,缓存思想是一种典型的空间换时间的方案,例如:某些资源存储在外部,但我们又需要经常去使用它,每次从外部中去找花费了很多时间,通过缓存将这些资源放在内存中,每次操作的时候就直接在内存中进行查找。

第一种写法:

public class Teacher {
	String name;
	int age;
	String sex;
	static Teacher teacher = null;
	private Teacher() {
		
	}
	public static Teacher getMethod() {
		if(teacher == null) {
			teacher = new Teacher();
		}
		return teacher ;
	}
}

这种写法比较简单,由私有构造器和一个公有静态工厂方法构成,在工厂方法中对teacher进行null判断,如果是null就new一个出来,最后返回teacher对象。

这种方法虽然可以实现延时加载,但是线程不安全。怎么解释囊?

如果有两条线程同时调用getmethod()方法,就有很大可能导致重复创建对象。

第二种写法:

public class Teacher {
    private static volatile Teacher teacher = null;
 
    private Teacher (){
     
    }
 
    public static Teacher getMethod(){
        synchronized (Teacher.class){
            if(teacher == null){
                teacher = new Teacher ();
            }
        }
        return teacher ;
    }    
}

这种写法考虑了线程安全,将对teacher的null判断以及new的部分使用synchronized进行加锁。同时,对teacher对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。这里需要注意的是:volatile关键字禁止指令重排优化语义直到jdk1.5以后才能正确工作。

第三种写法:

public class Teacher{
    private static volatile Teacher teacher= null;
    
    private Teacher(){}
    
    public static Teacher getMethod(){
        if(teacher == null){
            synchronized (Teacher.class){
                if(teacher == null){
                    teacher = new Teacher();
                }
            }
        }
        return teacher ;
    }    
}

这种写法相较于第二种写法有了更高的效率,被称为“双重检查锁”,因为进行双层判断当多线程进行排队等待之前进行一次判断,极大提升了并发度,进而提升了性能。因为在单例中需要new的情况非常少,绝大多数都是可以并行的读操作。因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,从而调高效率。

静态内部类法

public class Teacher{
    private static class GetTeacher {
        private static Teacher teacher= new Teacher();
    }
    
    private Teacher (){
    
    }
        
    public static Teacher getMethod(){
        return GetTeacher.teacher;
    }
}

由于静态内部类只会被加载一次,因此这种写法也是线程安全的,同时将Teacher实例放到一个静态内部类中,避免了静态的Teacher类型的teacher在加载的时候就创建了Teacher对象。