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