首页 > 代码库 > (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设计模式-单例模式