首页 > 代码库 > (3)Java设计模式-单例模式

(3)Java设计模式-单例模式

  单例模式(Singleton)是一种较为常用的设计模式,单例对象能保证在JVM虚拟中,该对象只有一个实例存在。

  1.(懒汉,线程不安全)

 1 //单例模式
 2 public class Singleton {
 3     // 私有化构造方法防止对象在外部被实例化
 4     private Singleton() {
 5         System.out.println("create");
 6     }
 7 
 8     // 私有化静态实例,防止实例被外部应用
 9     private static Singleton singleton = null;
10 
11     public static Singleton getObject() {
12         if (singleton == null) {
13             singleton = new Singleton();
14         }
15         return singleton;
16     }
17 
18 }

  测试类:

 1 public class test {
 2     public static void main(String[] args) {
 3         Singleton s1 = Singleton.getObject();
 4         System.out.println(s1.hashCode());//获取对象的编码,该编码是唯一值
 5         Singleton s2 = Singleton.getObject();
 6         System.out.println(s2.hashCode());
 7         Singleton s3 = Singleton.getObject();
 8         System.out.println(s3.hashCode());
 9     }
10 }

  运行结果:

create
366712642
366712642
366712642

从运行结果来看,对象只被实例化一次。且每次获取编码值都相同,这段代码可以满足基本需求,但是此类没有任何线程保护,遇到多线程环境,会出现问题。示例如下:

 1 //新增线程A类
 2 public class ThreadA implements Runnable {
 3 
 4     @Override
 5     public void run() {
 6         Singleton s1 = Singleton.getObject();
 7         System.out.println(s1.hashCode());
 8     }
 9 
10 }

  测试类

 1 public class test2 {
 2     public static void main(String[] args) {
 3         ThreadA2 a1 = new ThreadA2();
 4         ThreadA2 a2 = new ThreadA2();
 5         Thread therad1 = new Thread(a1);
 6         Thread therad2 = new Thread(a2);
 7         therad1.start();
 8         therad2.start();
 9     }
10 }

  运行结果:

create
79380705
create
1675705343

  这种毫无线程安全措施的懒汉模式,只适合在单线程的情况下运行,一旦加入多线程环境,会产生多个对象

  2.(懒汉,线程安全)

 1 //新的单例类
 2 public class Singleton2 {
 3     private Singleton2() {
 4         System.out.println("create");
 5     }
 6 
 7     private static Singleton2 singleton = null;
 8 
 9     // 加入synchronized关键字保证线程安全
10     public synchronized static Singleton2 getObject() {
11         if (singleton == null) {
12             singleton = new Singleton2();
13         }
14         return singleton;
15     }
16 
17 }
 1 //新增线程A2类,调用新的单例类方法
 2 public class ThreadA2 implements Runnable {
 3 
 4     @Override
 5     public void run() {
 6         Singleton2 s1 = Singleton2.getObject();
 7         System.out.println(s1.hashCode());
 8     }
 9 
10 }

  测试类

 1 public class test2 {
 2     public static void main(String[] args) {
 3         ThreadA2 a1 = new ThreadA2();
 4         ThreadA2 a2 = new ThreadA2();
 5         Thread therad1 = new Thread(a1);
 6         Thread therad2 = new Thread(a2);
 7         therad1.start();
 8         therad2.start();
 9     }
10 }

  运行结果

create
1715342245
1715342245

  新的懒汉模式加入synchronized关键字,保证线程安全,但是正是由于同步锁,导致多个线程调用该方法时必须依次运行,而代码除了第一次运行时对象为空时才需要有线程锁,其他的时候都不需要,使代码运行效率极低。

3.饿汉模式

//新的单例类
public class Singleton3 {
    private Singleton3() {
        System.out.println("create");
    }

    private static Singleton3 singleton = new Singleton3();

    public static Singleton3 getObject() {
        return singleton;
    }

}

  测试类

 1 public class test3 {
 2     public static void main(String[] args) {
 3         Singleton3 s1 = Singleton3.getObject();
 4         System.out.println(s1.hashCode());
 5         Singleton3 s2 = Singleton3.getObject();
 6         System.out.println(s2.hashCode());
 7         Singleton3 s3 = Singleton3.getObject();
 8         System.out.println(s3.hashCode());
 9     }
10 }

  运行结果

create
366712642
366712642
366712642

  新的单例类也完成了单例任务,但是单例对象在类被装载的时候就已经初始化了,导致类被装载的情况有很多,加入该类中包含其他静态方法,导致类被装载,那么这时候初始化单例对象明显是没有达到我们需要的效果。

4.静态内部类

 1 //新的单例类
 2 public class Singleton4 {
 3     private static class SingletonHolder {
 4         private static final Singleton4 singleton = new Singleton4();
 5     }
 6 
 7     private Singleton4() {
 8         System.out.println("create");
 9     }
10 
11     public static final Singleton4 getObject() {
12         return SingletonHolder.singleton;
13     }
14 }

  运行结果

create
366712642
366712642
366712642

  这种方法和方法3同样利用classloder的机制来保证初始化instance时只有一个线程,但是区别是方法3只要单例类被加载,那么单例对象就会被初始化,而这种方法,但单例类被加载,其静态内部类没有被主动使用,那么静态内部类的里的单例对象同样不会被初始化,只有静态内部类被主动使用,其内的单例对象才会被初始化。可以看出这种方法比其他方法合理的多。

5.枚举

1 public enum Singleton5 {
2     INSTANCE;
3 
4     private Singleton5() {
5         System.out.println("create");
6     }
7 }

  测试类

 1 public class test5 {
 2     public static void main(String[] args) {
 3         Singleton5 s1 = Singleton5.INSTANCE;
 4         System.out.println(s1.hashCode());
 5         Singleton5 s2 = Singleton5.INSTANCE;
 6         System.out.println(s2.hashCode());
 7         Singleton5 s3 = Singleton5.INSTANCE;
 8         System.out.println(s3.hashCode());
 9     }
10 }

  运行结果

1 create
2 366712642
3 366712642
4 366712642

  这个优秀的思想直接源于Joshua Bloch的《Effective Java》,这种方法不但能避免多线程同步问题,而且能防止反序列话重新创建新的对象。不过只有jdk1.5之后才可以用该方法,因为只有在此只有才加入了枚举的特性。

单例模式与类加载器的问题

 如果单例由不同的类加载器装入,那么我们就有可能得到多个单例类的实例,解决方法如下

 1     private static Class getClass(String classname)      
 2                                              throws ClassNotFoundException {     
 3           ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
 4           
 5           if(classLoader == null)     
 6              classLoader = Singleton.class.getClassLoader();     
 7           
 8           return (classLoader.loadClass(classname));     
 9        }     
10     }  

单例模式与序列化的问题

  由于序列化的特性,序列化会通过反射调用午无参的构造函数创建一个新的对象,导致单例模式被破坏就,解决办法:

 1 import java.io.Serializable;
 2 
 3 //新的单例类
 4 public class Singleton6 implements Serializable {
 5     private Singleton6() {
 6         System.out.println("create");
 7     }
 8 
 9     private static Singleton6 singleton = new Singleton6();
10 
11     public static Singleton6 getObject() {
12         return singleton;
13     }
14 
15     // 避免序列化带来的影响
16     public Object readResolve() {
17         return singleton;
18     }
19 }

 

  

(3)Java设计模式-单例模式