开始学习Java的设计模式,由于作了不少年C语言,因此语言基础的学习很快,可是面向过程向面向对象的编程思想的转变仍是须要耗费不少的代码量的。全部但愿经过设计模式的学习,能更深刻的学习。java
把学习过程当中的笔记,记录下来,只记干货。编程
第一部分:单例模式的内容设计模式
单例模式:类只能有一个实例。安全
类的特色:一、私有构造器;二、内部构造实例对象;三、对外提供获取惟一实例的public方法。ide
常见的单例模式实现有五种形式:函数
一、饿汉式。性能
二、懒汉式。学习
三、双重检查锁式。测试
四、静态内部类式。spa
五、枚举式。
如下分别介绍:
1、饿汉式
饿汉式单例特色:线程安全,不能延时加载,效率较高。
1 public class SingletonDemoE { 2 3 //内部构建惟一实例 4 private static SingletonDemoE instance = new SingletonDemoE(); 5 6 //私有化构造器 7 private SingletonDemoE(){ 8 9 } 10 11 //公共静态方法获取惟一实例化对象 12 public static SingletonDemoE getInstance(){ 13 return instance; 14 } 15 16 }
2、懒汉式
懒汉式单例特色:线程安全(须synchronized作方法同步),能够延时加载,效率较低。
1 public class SingletonDemoL { 2 3 //声明实例对象 4 private static SingletonDemoL instance; 5 6 //私有化构造器 7 private SingletonDemoL(){ 8 9 } 10 11 //公共静态方法获取惟一实例化对象,方法同步 12 public static synchronized SingletonDemoL getInstance(){ 13 if(instance == null){ 14 //第一次实例化时构建 15 instance = new SingletonDemoL(); 16 } 17 return instance; 18 } 19 20 }
3、双重检查锁式
结合了饿汉式和懒汉式的优势,但因为JVM底层内部模型缘由,偶尔会出问题,因此不建议使用,本文不赘语。
4、静态内部类式
静态内部类式单例特色:线程安全(须synchronized作方法同步),能够延时加载,效率较高。
1 public class SingletonDemoJ { 2 3 //静态内部类 4 private static class SingletonClassInstance { 5 private static final SingletonDemoJ instance = new SingletonDemoJ(); 6 } 7 8 //私有化构造器 9 private SingletonDemoJ(){ 10 11 } 12 13 //公共静态方法获取惟一实例化对象,方法同步 14 public static synchronized SingletonDemoJ getInstance(){ 15 return SingletonClassInstance.instance; 16 } 17 18 }
5、枚举式
枚举式单例特色:枚举是自然的单例,线程安全,不可延时加载,效率较高
1 public enum SingletonDemoM { 2 //枚举元素,自己就是单例模式 3 INSTANCE; 4 5 //实现本身的操做 6 public void singletonOperation(){ 7 8 } 9 }
第二部分:单例模式的破解(扩展)
单例模式的五种实现方式中,除枚举式是自然的单例不可破解以外,其余四种形式都可经过反射和反序列化的机制进行破解。
以懒汉式单例为例,首先分别看一下如何经过反射和反序列化的机制破解单例。
定义一个常规的懒汉式单例:
1 public class SingletonDemoAntiCrackL { 2 private static SingletonDemoAntiCrackL instance; 3 4 private SingletonDemoAntiCrackL(){ 5 6 } 7 8 public static synchronized SingletonDemoAntiCrackL getInstance(){ 9 if(instance == null){ 10 instance = new SingletonDemoAntiCrackL(); 11 } 12 return instance; 13 } 14 }
正常咱们建立多个单例的实例,都应该是同一个对象,以下测试Demo:
1 public class TestCrackDemo { 2 public static void main(String[] args) { 3 SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance(); 4 SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance(); 5 System.out.println("sL1 = " + sL1); 6 System.out.println("sL2 = " + sL2); 7 } 8 }
运行返回:
sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
利用反射机制破解单例,建立多个不一样的实例:
1 package com.corey.singleton; 2 3 import java.lang.reflect.Constructor; 4 5 public class TestCrackDemo { 6 public static void main(String[] args) { 7 SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance(); 8 SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance(); 9 System.out.println("sL1 = " + sL1); 10 System.out.println("sL2 = " + sL2); 11 12 //利用反射机制破解单例 13 try { 14 Class<SingletonDemoAntiCrackL> clazz = (Class<SingletonDemoAntiCrackL>)Class.forName("com.corey.singleton.SingletonDemoAntiCrackL"); 15 Constructor<SingletonDemoAntiCrackL> c = clazz.getDeclaredConstructor(null); 16 c.setAccessible(true); //跳过权限检查,能够访问私有属性和方法 17 SingletonDemoAntiCrackL sL3 = c.newInstance(null); //构建实例 18 SingletonDemoAntiCrackL sL4 = c.newInstance(null); 19 System.out.println("sL3 = " + sL3); 20 System.out.println("sL4 = " + sL4); 21 22 } catch (Exception e) { 23 e.printStackTrace(); 24 } 25 } 26 }
运行返回:
sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55 sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55 sL3 = com.corey.singleton.SingletonDemoAntiCrackL@15db9742 sL4 = com.corey.singleton.SingletonDemoAntiCrackL@6d06d69c
可见,经过反射构建的sL3和sL4两个对象,都是不一样的实例,破解了单例模式只能有一个实例的要求。
那么,修改单例的构造函数,能够应对反射机制的破解,代码以下:
1 public class SingletonDemoAntiCrackL { 2 private static SingletonDemoAntiCrackL instance; 3 4 private SingletonDemoAntiCrackL(){ 5 //私有构造器,增长实例检查,若已建立实例,则抛出异常 6 if(instance != null){ 7 throw new RuntimeException(); 8 } 9 } 10 11 public static synchronized SingletonDemoAntiCrackL getInstance(){ 12 if(instance == null){ 13 instance = new SingletonDemoAntiCrackL(); 14 } 15 return instance; 16 } 17 }
此时,在运行TestCrackDemo时,会抛出java.lang.reflect.InvocationTargetException异常,避免了经过反射机制建立多个实例的问题。
接下来,看下经过反序列化机制破解单例。
当单例的类实现了Serializable接口时,就能够经过反序列化机制破解,以下单例:
1 public class SingletonDemoAntiCrackL implements Serializable{ 2 private static SingletonDemoAntiCrackL instance; 3 4 private SingletonDemoAntiCrackL(){ 5 //私有构造器,增长实例检查,若已建立实例,则抛出异常 6 if(instance != null){ 7 throw new RuntimeException(); 8 } 9 } 10 11 public static synchronized SingletonDemoAntiCrackL getInstance(){ 12 if(instance == null){ 13 instance = new SingletonDemoAntiCrackL(); 14 } 15 return instance; 16 } 17 }
反序列化破解测试Demo:
1 package com.corey.singleton; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.ObjectInputStream; 7 import java.io.ObjectOutputStream; 8 import java.lang.reflect.Constructor; 9 10 public class TestCrackDemo { 11 public static void main(String[] args) { 12 SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance(); 13 SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance(); 14 System.out.println("sL1 = " + sL1); 15 System.out.println("sL2 = " + sL2); 16 17 //经过反序列化机制破解单例 18 try { 19 //序列化,将对象存入文件 20 FileOutputStream fos = new FileOutputStream("f:/fos.txt"); 21 ObjectOutputStream oos = new ObjectOutputStream(fos); 22 oos.writeObject(sL1); 23 oos.close(); 24 fos.close(); 25 //反序列化,从文件中读出对象 26 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("f:/fos.txt")); 27 SingletonDemoAntiCrackL sL5 = (SingletonDemoAntiCrackL)ois.readObject(); 28 System.out.println("sL5 = " + sL5); 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } 32 } 33 }
运行的结果:
sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55 sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55 sL5 = com.corey.singleton.SingletonDemoAntiCrackL@33909752
可见,反序列化出来的实例对象,是不一样的对象,即单例已被破解。
解决版本,单例类中重写readResolve方法,能够应对反射机制的破解,代码以下:
1 package com.corey.singleton; 2 3 import java.io.ObjectStreamException; 4 import java.io.Serializable; 5 6 /** 7 * 单例模式,懒汉式 8 * 线程安全,能延时加载,效率相对较低 9 * @author Corey 10 * 11 */ 12 public class SingletonDemoAntiCrackL implements Serializable{ 13 private static SingletonDemoAntiCrackL instance; 14 15 private SingletonDemoAntiCrackL(){ 16 //私有构造器,增长实例检查,若已建立实例,则抛出异常 17 if(instance != null){ 18 throw new RuntimeException(); 19 } 20 } 21 22 public static synchronized SingletonDemoAntiCrackL getInstance(){ 23 if(instance == null){ 24 instance = new SingletonDemoAntiCrackL(); 25 } 26 return instance; 27 } 28 29 private Object readResolve() throws ObjectStreamException{ 30 //反序列化时,直接返回对象 31 return instance; 32 } 33 }
修改后,再次运行TestCrackDemo,能够看到反序列化后,构建的仍然是同一个对象。
sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55 sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55 sL5 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
第三部分:单例模式各个实现方式的效率
采用以下代码,测试:
1 package com.corey.singleton; 2 3 import java.util.concurrent.CountDownLatch; 4 5 /** 6 * 测试单例模式的效率 7 * @author Corey 8 * 9 */ 10 public class TestEfficiencyDemo { 11 public static void main(String[] args) throws Exception { 12 13 int threadNum = 10; 14 long start = System.currentTimeMillis(); 15 16 final CountDownLatch cdl = new CountDownLatch(threadNum); 17 18 //建立10个线程 19 for(int k=0; k<threadNum; k++){ 20 new Thread(new Runnable() { 21 22 @Override 23 public void run() { 24 //每一个线程构建100万个实例对象 25 for(int i=0; i<1000000; i++){ 26 Object o = SingletonDemoE.getInstance(); 27 } 28 //每一个线程运行完毕,线程计数器减一 29 cdl.countDown(); 30 } 31 }).start(); 32 } 33 34 cdl.await();//main线程阻塞,直到现场计数器为0,才继续执行。 35 36 long end = System.currentTimeMillis(); 37 System.out.println("饿汉式总耗时:" + (end - start)); 38 } 39 }
运行结果:
饿汉式总耗时:17
以此类推,测试各个实现方式的单例的效率。注意,此处根据电脑性能以及电脑的运行状况不一样,结果都是不同的,甚至同一实现方式,屡次运行的结果也不同。
我这里的测试结果以下:
饿汉式总耗时:17 懒汉式总耗时:171 静态内部类式总耗时:165 枚举式总耗时:11
以上就是设计模式中的单例模式!