单例模式中懒汉模式的非线程安全问题的解决方法

非原创,转自 https://blog.csdn.net/noaman_wgs/article/details/53258710

单例模式中懒汉模式的非线程安全问题的解决方法

   单例模式中有饿汉模式和懒汉模式两种。饿汉模式也叫立即加载 ,即在get之前就已经创建实例instance;

     
     
  1. package singleton1;
  2. //饿汉模式(立即加载)
  3. public class Singleton {
  4. private static Singleton instance = new Singleton();
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. return instance;
  8. }
  9. }

而懒汉模式也叫延迟加载,即在get时才会被创建实例。懒汉模式在多线程的环境中就会出现多实例的情况,与单例模式相背离。如下代码可证:

     
     
  1. package singleton2;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. try {
  8. if(instance== null){
  9. Thread.sleep( 1000); //模拟延迟加载
  10. instance= new Singleton();
  11. }
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. return instance;
  16. }
  17. }
创建线程:

     
     
  1. package singleton2;
  2. public class ThreadA extends Thread{
  3. @Override
  4. public void run(){
  5. System.out.println(Singleton.getInstance().hashCode()); //根据实例对象哈希值是否相同来判断对象是否相同
  6. }
  7. }
创建运行类:

     
     
  1. package singleton2;
  2. public class Run {
  3. public static void main(String[] args) {
  4. ThreadA t1= new ThreadA();
  5. ThreadA t2= new ThreadA();
  6. ThreadA t3= new ThreadA();
  7. t1.start();
  8. t2.start();
  9. t3.start();
  10. }
  11. }
运行结果:

     
     
  1. 1321522194
  2. 2134502363
  3. 866891806
哈希值不同,所以对象不同,即不是单例模式。如何解决这个问题呢,下面提供了几种方法:


一、方法上声明synchronized关键字(效率低)


     
     
  1. package singleton2;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. //1 方法上声明synchronized关键字
  7. synchronized public static Singleton getInstance(){
  8. try {
  9. if(instance== null){
  10. Thread.sleep( 1000); //模拟延迟加载
  11. instance= new Singleton();
  12. }
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. return instance;
  17. }
  18. }

运行结果如下:

     
     
  1. 1876189785
  2. 1876189785
  3. 1876189785
哈希值相同,是单例模式的实现。但这种加了synchronized关键字后,多线程进行操作时只能等一个线程处理完,下一个线程才能进入同步方法中,效率低下。

二、同步代码块(效率低)


     
     
  1. package singleton3;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. try {
  8. // 2 使用同步代码块,但效率低
  9. synchronized(Singleton.class){
  10. if(instance== null){
  11. Thread.sleep( 1000); //模拟延迟加载
  12. instance= new Singleton();
  13. }
  14. }
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. return instance;
  19. }
  20. }

运行结果:

     
     
  1. 1619327594
  2. 1619327594
  3. 1619327594
       使用同步代码块方式也能解决上述问题,虽然不需要判断实例是否为null,但同样的也会带来效率低的毛病。

三、使用DCL(Double-Check Locking)双检查锁机制---(推荐


     
     
  1. public class Singleton {
  2. private static volatile Singleton instance = null;
  3. private Singleton(){}
  4. public static Singleton getInstance(){
  5. if(instance == null){
  6. synchronized(Singleton.class){
  7. if(instance == null){
  8. instance = new Singleton();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

这里为什么要用volatile修饰instance?
原因:在于instance = new Singleton()的时候,在内存中实际上是分3步执行的:
1)分配对象的内存空间:memory = allocate();
2)初始化对象:ctorInstance(memory);
3)指向分配的地址:instance =memory
多线程在执行的时候,2 3可能发生重排序。即有可能线程A执行到第3步的时候,读取到instance不为null,就返回。实际上此时还未执行第二部即未初始化。
加上volatile就可以避免2 3步重排序来保证线程安全。


四、使用静态内部类来实现单例模式


     
     
  1. package singleton6staticinnerclass;
  2. public class Singleton {
  3. //静态内部类进行初始化
  4. private static class SingletonHandler{
  5. private static Singleton instance = new Singleton();
  6. }
  7. private Singleton(){}
  8. public static Singleton getInstance(){
  9. return SingletonHandler.instance;
  10. }
  11. }

五、static代码块来实现单例模式


     
     
  1. package singleton7static;
  2. public class Singleton {
  3. private static Singleton instance = null;
  4. private Singleton(){}
  5. //静态代码块
  6. static{
  7. instance= new Singleton();
  8. }
  9. public static Singleton getInstance(){
  10. return instance;
  11. }
  12. }

还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。

























非原创,转自 https://blog.csdn.net/noaman_wgs/article/details/53258710

单例模式中懒汉模式的非线程安全问题的解决方法

   单例模式中有饿汉模式和懒汉模式两种。饿汉模式也叫立即加载 ,即在get之前就已经创建实例instance;

   
   
  1. package singleton1;
  2. //饿汉模式(立即加载)
  3. public class Singleton {
  4. private static Singleton instance = new Singleton();
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. return instance;
  8. }
  9. }

而懒汉模式也叫延迟加载,即在get时才会被创建实例。懒汉模式在多线程的环境中就会出现多实例的情况,与单例模式相背离。如下代码可证:

   
   
  1. package singleton2;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. try {
  8. if(instance== null){
  9. Thread.sleep( 1000); //模拟延迟加载
  10. instance= new Singleton();
  11. }
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. return instance;
  16. }
  17. }
创建线程:

   
   
  1. package singleton2;
  2. public class ThreadA extends Thread{
  3. @Override
  4. public void run(){
  5. System.out.println(Singleton.getInstance().hashCode()); //根据实例对象哈希值是否相同来判断对象是否相同
  6. }
  7. }
创建运行类:

   
   
  1. package singleton2;
  2. public class Run {
  3. public static void main(String[] args) {
  4. ThreadA t1= new ThreadA();
  5. ThreadA t2= new ThreadA();
  6. ThreadA t3= new ThreadA();
  7. t1.start();
  8. t2.start();
  9. t3.start();
  10. }
  11. }
运行结果:

   
   
  1. 1321522194
  2. 2134502363
  3. 866891806
哈希值不同,所以对象不同,即不是单例模式。如何解决这个问题呢,下面提供了几种方法:


一、方法上声明synchronized关键字(效率低)


   
   
  1. package singleton2;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. //1 方法上声明synchronized关键字
  7. synchronized public static Singleton getInstance(){
  8. try {
  9. if(instance== null){
  10. Thread.sleep( 1000); //模拟延迟加载
  11. instance= new Singleton();
  12. }
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. return instance;
  17. }
  18. }

运行结果如下:

   
   
  1. 1876189785
  2. 1876189785
  3. 1876189785
哈希值相同,是单例模式的实现。但这种加了synchronized关键字后,多线程进行操作时只能等一个线程处理完,下一个线程才能进入同步方法中,效率低下。

二、同步代码块(效率低)


   
   
  1. package singleton3;
  2. //懒汉模式
  3. public class Singleton {
  4. private static Singleton instance = null;
  5. private Singleton(){}
  6. public static Singleton getInstance(){
  7. try {
  8. // 2 使用同步代码块,但效率低
  9. synchronized(Singleton.class){
  10. if(instance== null){
  11. Thread.sleep( 1000); //模拟延迟加载
  12. instance= new Singleton();
  13. }
  14. }
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. return instance;
  19. }
  20. }

运行结果:

   
   
  1. 1619327594
  2. 1619327594
  3. 1619327594
       使用同步代码块方式也能解决上述问题,虽然不需要判断实例是否为null,但同样的也会带来效率低的毛病。

三、使用DCL(Double-Check Locking)双检查锁机制---(推荐


   
   
  1. public class Singleton {
  2. private static volatile Singleton instance = null;
  3. private Singleton(){}
  4. public static Singleton getInstance(){
  5. if(instance == null){
  6. synchronized(Singleton.class){
  7. if(instance == null){
  8. instance = new Singleton();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

这里为什么要用volatile修饰instance?
原因:在于instance = new Singleton()的时候,在内存中实际上是分3步执行的:
1)分配对象的内存空间:memory = allocate();
2)初始化对象:ctorInstance(memory);
3)指向分配的地址:instance =memory
多线程在执行的时候,2 3可能发生重排序。即有可能线程A执行到第3步的时候,读取到instance不为null,就返回。实际上此时还未执行第二部即未初始化。
加上volatile就可以避免2 3步重排序来保证线程安全。


四、使用静态内部类来实现单例模式


   
   
  1. package singleton6staticinnerclass;
  2. public class Singleton {
  3. //静态内部类进行初始化
  4. private static class SingletonHandler{
  5. private static Singleton instance = new Singleton();
  6. }
  7. private Singleton(){}
  8. public static Singleton getInstance(){
  9. return SingletonHandler.instance;
  10. }
  11. }

五、static代码块来实现单例模式


   
   
  1. package singleton7static;
  2. public class Singleton {
  3. private static Singleton instance = null;
  4. private Singleton(){}
  5. //静态代码块
  6. static{
  7. instance= new Singleton();
  8. }
  9. public static Singleton getInstance(){
  10. return instance;
  11. }
  12. }

还有序列化和反序列化的单例模式来实现,或者使用枚举类来实现,在此就不一一介绍了,有兴趣可以百度下。

























猜你喜欢

转载自blog.csdn.net/weixin_42581112/article/details/89197897