单例模式
showMeTheCodes 人气:2这篇文章中我会用8种写法来对单例模式进行优化,以达到最完美的效果
但是说实话在平常我们进行代码编写的时候用不着那么完美
单例模式
什么是单例模式?
简单的说就是只能new出来一个实例
第一种写法
饿汉式:
优点:简单实用
缺点:不论该对象是否会被用到,都提前将对象实例化
1.首先我们创建出一个静态的不可更改的变量Instance
2.我们将该类的构造方法的权限设置为private,防止其他类new对象
3.设置该对象的get方法
1 /** 2 * 饿汉式 3 * 类加载到内存后,就实例化一个单例,JVM保证线程安全 4 * (JVM保证每一个class只会露到内存一次,那么static变量在class露到内存之后马上进行初始化,所以static变量也保证初始化这一次) 5 * 简单实用,推荐使用 6 * 唯一缺点:不管用到与否,类加载时就完成实例化 7 */ 8 public class Mgr01 { 9 private static final Mgr01 Instance = new Mgr01(); 10 11 private Mgr01(){} 12 13 public static Mgr01 getInstance(){return Instance;} 14 15 public static void main(String[] args) { 16 /** 17 * 调用静态方法常用的两种方式 18 * 1.new对象调用静态方法 19 * 2.类名打点调用静态方法 20 * 这里构造方法设为私有访问控制符,不能new对象 21 */ 22 Mgr01 m1 = Mgr01.getInstance(); 23 Mgr01 m2 = Mgr01.getInstance(); 24 System.out.println(m1 == m2); 25 } 26 }
第二种写法
第二种写法与第一种写法几乎没有区别,只是使用了静态代码块进行对象的初始化
但是该写法仍然没有解决类加载时初始化的问题
1 /** 2 * 与Mgr01意思相同 3 */ 4 public class Mgr02 { 5 //这里加上final没有初始化,但是在下面必须加static静态代码块进行初始化 6 private static final Mgr02 Instance; 7 8 static{ 9 Instance = new Mgr02(); 10 } 11 12 private Mgr02(){} 13 14 public static Mgr02 getInstance(){return Instance;} 15 16 public static void main(String[] args) { 17 Mgr02 m1 = Mgr02.getInstance(); 18 Mgr02 m2 = Mgr02.getInstance(); 19 System.out.println(m1 == m2); 20 } 21 }
第三种写法
懒汉式:
优点:在需要的时候进行对象初始化,解决了以上两种写法的缺点
缺点:带来了线程不安全的问题
在进行判断是否存在Instance实例时,我们假设有现后两个线程,一号线程刚判断完发现没有实例,正准备进行new时,二号线程突然进入,判断结果同样为没有Instance实例,这时就会有两个线程先后执行new操作
1 /** 2 * 懒汉式 3 * 达到了按需初始化的目的,但是带来了线程不安全的问题 4 */ 5 public class Mgr03 { 6 //这里不能加final,因为加上final就必须进行初始化new对象 7 private static Mgr03 Instance; 8 9 private Mgr03(){} 10 11 public static Mgr03 getInstance(){ 12 if (Instance == null){ 13 //多线程同时打入的时候容易在这里产生误差 14 Instance = new Mgr03(); 15 } 16 return Instance; 17 } 18 19 public static void main(String[] args) { 20 Mgr03 m1 = Mgr03.getInstance(); 21 Mgr03 m2 = Mgr03.getInstance(); 22 System.out.println(m1 == m2); 23 } 24 }
第四种写法
在getInstance方法上加锁来保证线程安全,但是如果每个线程在执行getInstance时都进行加锁操作,那么就会降低程序执行效率
1 /** 2 * 增加了线程的安全性,但是降低了程序执行效率 3 */ 4 public class Mgr04 { 5 private static Mgr04 Instance; 6 7 private Mgr04(){} 8 9 public static synchronized Mgr04 getInstance(){ 10 if (Instance == null){ 11 Instance = new Mgr04(); 12 } 13 return Instance; 14 } 15 public static void main(String[] args) { 16 Mgr04 m1 = Mgr04.getInstance(); 17 Mgr04 m2 = Mgr04.getInstance(); 18 System.out.println(m1 == m2); 19 } 20 }
第五种写法
试图通过同步代码块的方式在保证线程安全的前提下提高效率
同步代码块只加在需要new实例的时候
这样虽然提高了程序执行效率,但是显然是不能保证线程安全的
假设有两个线程:线程一在if条件判断结束后发现没有实例,正在往下执行但是还没有进入同步代码块时,线程二进入也同样判断没有实例,线程二比线程一提前拿到锁new出来实例后释放锁,等到线程二释放锁之后线程一又执行方法new出来实例。这样就导致了线程的不安全性
1 public class Mgr05 { 2 private static Mgr05 Instance; 3 4 private Mgr05(){} 5 6 public static Mgr05 getInstance(){ 7 if (Instance == null){ 8 /** 9 * 试图通过同步代码块的方式提高效率 10 * 但是这里又很容易造成线程不安全问题 11 */ 12 synchronized (Mgr05.class){ 13 Instance = new Mgr05(); 14 } 15 } 16 return Instance; 17 } 18 public static void main(String[] args) { 19 Mgr05 m1 = Mgr05.getInstance(); 20 Mgr05 m2 = Mgr05.getInstance(); 21 System.out.println(m1 == m2); 22 } 23 }
第六种写法(比较完美的写法之一)
双重if判断来保证只有一个实例对象
这样即不会出现线程不安全问题,又保证了不会随着类加载而创建出来实例
1 public class Mgr06 { 2 //加volatile主要是为了防止指令重排 3 private static volatile Mgr06 Instance; 4 5 private Mgr06(){} 6 7 public static Mgr06 getInstance(){ 8 //第一个进入方法的线程进行Instance实例是否存在的判断 9 if (Instance == null){ 10 //同步代码块进行加锁 11 synchronized (Mgr06.class){ 12 //双重锁机制进行判断Instance对象是否被实例 13 if (Instance == null){ 14 Instance = new Mgr06(); 15 } 16 } 17 } 18 return Instance; 19 } 20 public static void main(String[] args) { 21 Mgr06 m1 = Mgr06.getInstance(); 22 Mgr06 m2 = Mgr06.getInstance(); 23 System.out.println(m1 == m2); 24 } 25 }
第七种写法(比较完美的写法之一)
使用静态内部类来保证只有一个实例
静态内部类在加载类的时候是不会被加载的,而getInstance方法在执行返回Instance实例调用静态内部类时,静态内部类才会被加载,保证了在使用时才会创建实例
JVM只会加载一次类,同样也只会加载一次静态内部类,这样就保证了线程安全,只会产生一个实例对象
1 /** 2 * 使用静态内部类保证单例,同时也保证线程安全 3 * 线程安全是用过JVM机制来保证的 4 * JVM只会加载一次Mgr07这个类,只会加载一次Mgr07Holder这个内部类 5 * 这也就保证了只会生成一个Instance实例 6 */ 7 public class Mgr07 { 8 private Mgr07(){} 9 10 /** 11 * 使用静态内部类进行实现 12 * 当Mgr07这个类被加载时,里面的内部类是不会被加载的,保证了类加载时不完成实例化 13 * 当调用getInstance方法时内部类才会加载,也保证了只有一个实例 14 */ 15 private static class Mgr07Holder{ 16 private static final Mgr07 Instance = new Mgr07(); 17 } 18 19 public static Mgr07 getInstance(){ 20 return Mgr07Holder.Instance; 21 } 22 23 public static void main(String[] args) { 24 Mgr07 m1 = Mgr07.getInstance(); 25 Mgr07 m2 = Mgr07.getInstance(); 26 System.out.println(m1 == m2); 27 } 28 }
第八种写法(最完美的写法)
枚举单例模式,枚举类中没有构造方法,保证了仅仅实例化一个对象
只有在调用Instance实例的时候才会创建实例,保证了在使用时才会创建实例
1 /** 2 * 枚举单例模式:通过枚举进行单例模式的实例创建 3 * 原因:枚举类中没有构造方法 4 * 不仅可以解决线程同步,还可以防止反序列化 5 */ 6 public enum Mgr08 { 7 8 Instance; 9 10 public static void main(String[] args) { 11 Mgr08 m1 = Mgr08.Instance; 12 Mgr08 m2 = Mgr08.Instance; 13 System.out.println(m1 == m2); 14 } 15 }
总结
单例模式在现在编写代码时使用较少,spring帮我们保证了仅仅初始化一个实例对象,但是我们也应该理解单例模式的实现原理
加载全部内容