多线程进阶-JUC

90 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天

1. 彻底玩转单例模式

饿汉式、DCL懒汉式

饿汉式

 //饿汉式单例模式
 public class Hungry {
 ​
     //浪费空间
     private byte[] date1 = new byte[1024*1024];
     private byte[] date2 = new byte[1024*1024];
     private byte[] date3 = new byte[1024*1024];
     private byte[] date4 = new byte[1024*1024];
     private byte[] date5 = new byte[1024*1024];
 ​
 ​
     private Hungry(){
 ​
     }
 ​
     private final static Hungry HUNGRY = new Hungry();
 ​
     public static  Hungry getInstance(){
         return HUNGRY;
     }
 }

懒汉式

  1. 第一种实现方式(不推荐)
 private static Singleton1 singleton;
 private Singleton1() {
     
 }
 ​
 public static Singleton1 getInstance() {
     
     if(singleton==null) 
     {
       singleton=new Singleton1();
     }
     
     return singleton;
 }

} 缺点:只能在单线程下使用,多线程下会产生多个对象。

  1. 第二种实现方式(不推荐)

     public class Singleton2 {
       private static Singleton2 singleton;
       private Singleton2() {
           
       }
       public static synchronized Singleton2 getInstance() {
           if(singleton==null) {
               singleton=new Singleton2();
           }
           return singleton;
       }
       
     }
    

    缺点:加锁进行同步,虽然可以保证单例,但效率太低,浪费大量时间。

  2. 第三种实现方式(推荐) 著名的双重检查机制

 public class Singleton3 {
    private volatile static Singleton3 singleton;
    //private static Singleton singleton;  
    private Singleton3() {
        
    }
    
    public static Singleton3 getInstance() {
        if(singleton==null) {
            
            synchronized (Singleton3.class) {
             if(singleton==null) {
                 singleton=new Singleton3();
             }
         }
        }
        
        return singleton;
    }
    
 }

优点:保证单例的同时,也提高了效率。

注意:singleton前面要加volatile关键字来保证程序运行的有序性,否则多线程访问下可能会出现对象未初始化错误!

说明:在内存中,创建一个变量需要三步:1.申请一块内存 ;2.调用构造方法初始化 ;3 分配一个指针指向这块内存

在编译原理中,有一个重要的内容叫做编译器优化,即在不改变原来语义的情况下,调整语句的执行顺序,来让程序运行的更快

因此存在这样一种情况,有两个线程A、B同时访问getInstance方法

A线程判断对象为空,没来得及进行第二次判断,(时间片用完了,B线程进入) B线程判断对象为空,执行创建变量的3步,先申请一块内存,后分配一个指针指向这块内存,但还没有进行初始化(时间片用完了,A线程进入) A线程接着执行,发现此时singleton已经不为空了,所以直接返回,但此时返回的singleton对象虽然B线程已经new了,但还没有初始化这个实例并没有构造完成,此时如果A线程使用这个实例,程序就会出现对象未初始化错误了。

 //懒汉式单例模式
 public class LazyMan {
 ​
     private static boolean key = false;
 ​
     private LazyMan(){
         synchronized (LazyMan.class){
             if (key==false){
                 key=true;
             }
             else{
                 throw new RuntimeException("不要试图使用反射破坏异常");
             }
         }
         System.out.println(Thread.currentThread().getName()+" ok");
     }
     private volatile static LazyMan lazyMan;
 ​
     //双重检测锁模式 简称DCL懒汉式
     public static LazyMan getInstance(){
         //需要加锁
         if(lazyMan==null){
             synchronized (LazyMan.class){
                 if(lazyMan==null){
                     lazyMan=new LazyMan();
                     /**
                      * 1、分配内存空间
                      * 2、执行构造方法,初始化对象
                      * 3、把这个对象指向这个空间
                      *
                      *  就有可能出现指令重排问题
                      *  比如执行的顺序是1 3 2 等
                      *  我们就可以添加volatile保证指令重排问题
                      */
                 }
             }
         }
         return lazyMan;
     }
     //单线程下 是ok的
     //但是如果是并发的
     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
         //Java中有反射
 //        LazyMan instance = LazyMan.getInstance();
         Field key = LazyMan.class.getDeclaredField("key");
         key.setAccessible(true);
         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true); //无视了私有的构造器
         LazyMan lazyMan1 = declaredConstructor.newInstance();
         key.set(lazyMan1,false);
         LazyMan instance = declaredConstructor.newInstance();
 ​
         System.out.println(instance);
         System.out.println(lazyMan1);
         System.out.println(instance == lazyMan1);
     }
 }
 ​

静态内部类

 //静态内部类
 public class Holder {
     private Holder(){
 ​
     }
     public static Holder getInstance(){
         return InnerClass.holder;
     }
     public static class InnerClass{
         private static final Holder holder = new Holder();
     }
 }
 ​

单例不安全, 因为反射

枚举

 //enum 是什么? enum本身就是一个Class 类
 public enum EnumSingle {
     INSTANCE;
     public EnumSingle getInstance(){
         return INSTANCE;
     }
 }
 ​
 class Test{
     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
         EnumSingle instance1 = EnumSingle.INSTANCE;
         Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
         declaredConstructor.setAccessible(true);
         //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()
 ​
         EnumSingle instance2 = declaredConstructor.newInstance();
         System.out.println(instance1);
         System.out.println(instance2);
     }
 }
 ​

使用枚举,我们就可以防止反射破坏了。

image-20221012180514984

枚举类型使用JAD最终反编译后源码:

如果我们看idea 的文件:会发现idea骗了我们,居然告诉我们是有有参构造的,我们使用jad进行反编译。

image-20221012180626238

image-20221012180642895

 public final class EnumSingle extends Enum
 {
 ​
     public static EnumSingle[] values()
     {
         return (EnumSingle[])$VALUES.clone();
     }
 ​
     public static EnumSingle valueOf(String name)
     {
         return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
     }
 ​
     private EnumSingle(String s, int i)
     {
         super(s, i);
     }
 ​
     public EnumSingle getInstance()
     {
         return INSTANCE;
     }
 ​
     public static final EnumSingle INSTANCE;
     private static final EnumSingle $VALUES[];
 ​
     static 
     {
         INSTANCE = new EnumSingle("INSTANCE", 0);
         $VALUES = (new EnumSingle[] {
             INSTANCE
         });
     }
 }
 ​