主頁 >  其他 > JUC并發編程(十)--Volatile、原子性以及單例模式的應用

JUC并發編程(十)--Volatile、原子性以及單例模式的應用

2021-02-01 11:15:46 其他

JUC并發編程(十)--Volatile、原子性以及單例模式的應用

  • 一、JMM
    • 1、什么是JMM
    • 2、JMM的約定
    • 3、八種操作
  • 二、Volatile
    • 1、可見性
    • 2、不保證原子性
      • 原子類
    • 3、禁止指令重排
  • 三、單例模式
    • 1、常見的懶漢模式
    • 2、破解一般的懶漢模式
    • 3、使用列舉實作單例模式

一、JMM

1、什么是JMM

JMM是一種Java記憶體模型,是一種概念性的約定,而不是實際存在的東西,

2、JMM的約定

  • 執行緒解鎖前,必須把變數立即刷回主存;
    我們知道,一個執行緒作業時,會將主存中的變數復制一份給執行緒自己的記憶體,作為一個副本,執行緒對此變數的操作,都是在副本上的操作,所以當執行緒運行完畢,解鎖的時候,必須將副本的值同步回主存,

  • 執行緒加鎖前,必須讀取主存中最新的變數值,然后復制到自己的作業記憶體中;

  • 加鎖和解鎖是同一把鎖,

3、八種操作

在這里插入圖片描述

  • 從主存中read操作和load到執行緒作業記憶體中,是一組操作;
  • 執行緒的執行引擎使用此變數,并在使用之后回傳此變數,也是一組操作;
  • 從作業記憶體中保存此變數(store操作),并寫回主存,也是一組操作;
  • 加鎖(lock)和解鎖(unlock),也是一組操作,
    在這里插入圖片描述
    這里有個問題,現在執行緒A和執行緒B都從主存中讀取了Flag,然后執行緒B先修改了值,并寫回了記憶體,這時執行緒A不能及時得到此訊息,即不能及時可見此變數,所以這時我們就需要Volatile了,

二、Volatile

1、可見性

先上代碼:

package com.zhan.juc.volatiletest;

import java.util.concurrent.TimeUnit;

/**
 * @Author Zhanzhan
 * @Date 2021/1/28 21:07
 */
public class JMMDemo {

    private static int num = 0;

    public static void main(String[] args){
        new Thread(() -> {
            while (num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        num = 1;
        System.out.println(num);
    }
}

我們看上面的代碼,可以看到,有一個執行緒,里面while回圈,不停的判斷num的值,然后在執行緒的下面將num值賦為1,那么這時我們開出來的這個執行緒會停掉嗎?
看結果:
在這里插入圖片描述
我們發現,并沒有停,這是為什么呢?就像上面說的那樣,執行緒將num的值從主存中復制了一份,然后主存中num值的變動,執行緒并沒有看到,
那么要如何解決?接下來看:

package com.zhan.juc.volatiletest;

import java.util.concurrent.TimeUnit;

/**
 * @Author Zhanzhan
 * @Date 2021/1/28 21:07
 */
public class JMMDemo {

    private volatile static int num = 0;

    public static void main(String[] args){
        new Thread(() -> {
            while (num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        num = 1;
        System.out.println(num);
    }
}

我們看到,上面的代碼中num用了volatile來修飾,然后看結果:
在這里插入圖片描述
我們發現,結果符合我們的預期,執行緒看到了主存中num的值的變動,

2、不保證原子性

什么是原子性:不可分割,執行緒A在執行任務的時候,不能被打擾,也不能被分割,要么同時成功,要么同時失敗,
廢話不多說,上代碼:

package com.zhan.juc.volatiletest;

/**
 * 測驗volatile不保證原子性
 *
 * @Author Zhanzhan
 * @Date 2021/1/28 21:19
 */
public class Demo {
    private volatile static int num = 0;

    public static void add() {
        num++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        // 判斷當前的執行緒是否大于兩條
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " num=" + num);
    }
}

如果執行緒能保證原子性,那么輸出的結果應該是num=20000,現在我們看結果:
在這里插入圖片描述
發現,volatile不能保證原子性,
那這時我們就有一個問題,如果不使用lock和synchronized,怎么保證原子性?

原子類

上代碼:

package com.zhan.juc.volatiletest;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 測驗volatile不保證原子性
 *
 * @Author Zhanzhan
 * @Date 2021/1/28 21:19
 */
public class Demo {
//    private volatile static int num = 0;

    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add() {
//        num++; // 不是一個原子性操作
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        // 判斷當前的執行緒是否大于兩條
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " num=" + num);
    }
}

看結果:
在這里插入圖片描述

3、禁止指令重排

什么是指令重排:計算機并不是按照我們寫的程式的順序那樣去執行的,會進行指令優化,
源代碼 =》編譯器優化的重排=》指令并行也可能的重排=》記憶體系統的重排=》執行

處理器在進行指令重排的時候,會考慮資料之間的依賴性

int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4

我們期望的順序:1234
但可能編程的執行順序:2134   1324

舉例說明,指令重排可能引發的問題:
假設有兩個執行緒A和B,然后有四個變數:a b x y
a b x y四個值默認為0

執行緒A執行緒B
x = ay = b
b = 1a = 2

執行緒A執行了兩個操作:x = a 和 b = 1;
執行緒B執行了兩個操作:y = b 和 a = 2;
在執行緒A執行 x = a后,執行緒B執行了 y = b,
這時我們預期的結果是 x = 0 和 y = 0;
但是現在執行緒A中的變數沒有依賴關系,執行緒B中的變數也沒有依賴關系,所以執行緒A和執行緒B就有可能發生指令重排,會造成如下操作:

執行緒A執行緒B
b = 1a = 2
x = ay = b

這時,得到的結果就為 x = 2 和 y = 1;

那么如何解決呢?
我們可以用volatile修飾,來避免指令重排,volatile會加一個記憶體屏障,來保證特定操作的執行順序以及記憶體可見性,
在這里插入圖片描述

三、單例模式

1、常見的懶漢模式

這里我們跳過餓漢模式,直接上懶漢模式:

package com.zhan.juc.volatiletest.single;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static LazyMan lazyMan;

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 無檢測的單例模式
     *
     * @return
     */
    public static LazyMan getInstanceByNotSafe() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    
    public static void main(String[] args) {
        /**
         * 測驗多執行緒下,這種單例模式是否安全
         */
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstanceByNotSafe();
            }).start();
        }
    }
}

這樣寫對嗎?看結果:
在這里插入圖片描述
我們發現不是單例的,那么一般的懶漢模式要怎么寫呢?

package com.zhan.juc.volatiletest.single;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static LazyMan lazyMan;

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 雙重檢測鎖 的 懶漢單例模式  DCL懶漢式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

看結果:
在這里插入圖片描述
符合預期,是單例的,但是這里會有一個問題,new 單例物件的這個操作,不是一個原子操作,即 lazyMan = new LazyMan(); // 不是一個原子性操作
這一步經過了那幾個操作:

  1. 分配記憶體空間;
  2. 執行構造方法,初始化物件;
  3. 把這個物件指向這個空間,

我們正常認為的執行順序是 1=》2=》3
但是有可能會指令重排,執行的順序是 1=》3=》2
如果此時有A執行緒執行的順序是1=》3=》2,那么執行到3這一步時,有個B執行緒也進來了,就會發現lazyMan 這個物件不為null,于是直接回傳,但是此時lazyMan 還沒完成構造,就會引發問題,所以,就要使用volatile來修飾 lazyMan ,禁止指令重排,于是代碼如下:

package com.zhan.juc.volatiletest.single;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修飾,禁止指令重排

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 雙重檢測鎖 的 懶漢單例模式  DCL懶漢式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配記憶體空間;
                     * 2、執行構造方法,初始化物件;
                     * 3、把這個物件指向這個空間,
                     */
                    lazyMan = new LazyMan(); // 不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

2、破解一般的懶漢模式

我們一般就認為,上面的那種單例模式,沒啥毛病了,
但是!依然可以有方法破解上述的單例模式,怎么破解呢?用反射!
上代碼:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修飾,禁止指令重排

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 雙重檢測鎖 的 懶漢單例模式  DCL懶漢式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配記憶體空間;
                     * 2、執行構造方法,初始化物件;
                     * 3、把這個物件指向這個空間,
                     */
                    lazyMan = new LazyMan(); // 不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解單例模式
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 獲取構造器
        declaredConstructor.setAccessible(true); // 無視私有構造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通過反射創建物件

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看結果:
在這里插入圖片描述
我們看到,這兩個物件實體的hashCode不一樣,是兩個不同的物件實體,于是,我們得出結論,一般的懶漢模式就這樣被反射破解了,那么有應對方法嗎?既然是獲取私有構造器,來創建實體,那么我們在私有構造器上也加上校驗呢?來看:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修飾,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (lazyMan != null){
                throw new RuntimeException("不要試圖使用反射破壞例外");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 雙重檢測鎖 的 懶漢單例模式  DCL懶漢式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配記憶體空間;
                     * 2、執行構造方法,初始化物件;
                     * 3、把這個物件指向這個空間,
                     */
                    lazyMan = new LazyMan(); // 不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解單例模式
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 獲取構造器
        declaredConstructor.setAccessible(true); // 無視私有構造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通過反射創建物件

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看結果:
在這里插入圖片描述
我們發現,這種方法被抵擋了,那么,還有可能破解這種單例模式嗎?

我們思考下,剛才那種破解,第一個物件實體是通過單例模式來創建的,第二個物件實體是通過反射來創建的,那么如果兩個物件都通過反射來創建呢?上代碼:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修飾,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (lazyMan != null){
                throw new RuntimeException("不要試圖使用反射破壞例外");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 雙重檢測鎖 的 懶漢單例模式  DCL懶漢式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配記憶體空間;
                     * 2、執行構造方法,初始化物件;
                     * 3、把這個物件指向這個空間,
                     */
                    lazyMan = new LazyMan(); // 不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解單例模式
//        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 獲取構造器
        declaredConstructor.setAccessible(true); // 無視私有構造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通過反射創建物件
        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看結果:
在這里插入圖片描述
非常的amazing啊,竟然又被破解了,既然這樣,我就較上勁兒了,還有什么方法能抵擋嗎?有的,我們用下紅綠燈模式,就是通過設定一個標識,來再次進行校驗,上代碼:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static boolean flag = false;

    private volatile static LazyMan lazyMan; // 使用volatile修飾,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (!flag){
                flag = true;
            } else {
                throw new RuntimeException("不要試圖使用反射破壞單例");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 雙重檢測鎖 的 懶漢單例模式  DCL懶漢式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配記憶體空間;
                     * 2、執行構造方法,初始化物件;
                     * 3、把這個物件指向這個空間,
                     */
                    lazyMan = new LazyMan(); // 不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解單例模式
//        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 獲取構造器
        declaredConstructor.setAccessible(true); // 無視私有構造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通過反射創建物件
        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看結果:
在這里插入圖片描述
符合預期,抵擋了這種方法,那到現在了,還有可能破解單例嗎?有的,就是,如果我知道了你設定的識別符號,我直接對識別符號進行修改呢?

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

/**
 * 單例模式--懶漢
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static boolean flag = false;

    private volatile static LazyMan lazyMan; // 使用volatile修飾,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (!flag){
                flag = true;
            } else {
                throw new RuntimeException("不要試圖使用反射破壞單例");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 雙重檢測鎖 的 懶漢單例模式  DCL懶漢式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配記憶體空間;
                     * 2、執行構造方法,初始化物件;
                     * 3、把這個物件指向這個空間,
                     */
                    lazyMan = new LazyMan(); // 不是一個原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解單例模式
//        LazyMan instance = LazyMan.getInstance();

        Field flag = LazyMan.class.getDeclaredField("flag");// 假設我們知道了識別符號的名稱
        flag.setAccessible(true); // 無視私有權限

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 獲取構造器
        declaredConstructor.setAccessible(true); // 無視私有構造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通過反射創建物件

        // 更改識別符號
        flag.set(instance2, false);

        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看結果:
在這里插入圖片描述
我們發現,又被破壞了,那么我們如何解決呢?看反射中newInstance()的原始碼:

/**
     * Uses the constructor represented by this {@code Constructor} object to
     * create and initialize a new instance of the constructor's
     * declaring class, with the specified initialization parameters.
     * Individual parameters are automatically unwrapped to match
     * primitive formal parameters, and both primitive and reference
     * parameters are subject to method invocation conversions as necessary.
     *
     * <p>If the number of formal parameters required by the underlying constructor
     * is 0, the supplied {@code initargs} array may be of length 0 or null.
     *
     * <p>If the constructor's declaring class is an inner class in a
     * non-static context, the first argument to the constructor needs
     * to be the enclosing instance; see section 15.9.3 of
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * <p>If the required access and argument checks succeed and the
     * instantiation will proceed, the constructor's declaring class
     * is initialized if it has not already been initialized.
     *
     * <p>If the constructor completes normally, returns the newly
     * created and initialized instance.
     *
     * @param initargs array of objects to be passed as arguments to
     * the constructor call; values of primitive types are wrapped in
     * a wrapper object of the appropriate type (e.g. a {@code float}
     * in a {@link java.lang.Float Float})
     *
     * @return a new object created by calling the constructor
     * this object represents
     *
     * @exception IllegalAccessException    if this {@code Constructor} object
     *              is enforcing Java language access control and the underlying
     *              constructor is inaccessible.
     * @exception IllegalArgumentException  if the number of actual
     *              and formal parameters differ; if an unwrapping
     *              conversion for primitive arguments fails; or if,
     *              after possible unwrapping, a parameter value
     *              cannot be converted to the corresponding formal
     *              parameter type by a method invocation conversion; if
     *              this constructor pertains to an enum type.
     * @exception InstantiationException    if the class that declares the
     *              underlying constructor represents an abstract class.
     * @exception InvocationTargetException if the underlying constructor
     *              throws an exception.
     * @exception ExceptionInInitializerError if the initialization provoked
     *              by this method fails.
     */
    @CallerSensitive
    @ForceInline // to ensure Reflection.getCallerClass optimization
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, clazz, modifiers);
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

我們看到:
在這里插入圖片描述

3、使用列舉實作單例模式

上代碼:

package com.zhan.juc.volatiletest.single;

/**
 * 列舉實作單例模式
 * @Author Zhanzhan
 * @Date 2021/1/30 14:34
 */
public class EnumSingle {

    enum EnumTest{
        INSTANCE;

        private EnumSingle enumSingle = null;

        private EnumTest(){
            enumSingle = new EnumSingle();
        }

        public EnumSingle getEnumSingle(){
            return enumSingle;
        }
    }


    public static void main(String[] args){
        EnumSingle instance1 = EnumTest.INSTANCE.getEnumSingle();
        EnumSingle instance2 = EnumTest.INSTANCE.getEnumSingle();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

看結果:
在這里插入圖片描述
符合預期,是同一個物件,
看反射是否能破壞此單例模式:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 列舉實作單例模式
 * @Author Zhanzhan
 * @Date 2021/1/30 14:34
 */
public class EnumSingle {

    enum EnumTest{
        INSTANCE;

        private EnumSingle enumSingle = null;

        private EnumTest(){
            enumSingle = new EnumSingle();
        }

        public EnumSingle getEnumSingle(){
            return enumSingle;
        }
    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumTest> declaredConstructor = EnumTest.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);

        EnumTest instance1 = declaredConstructor.newInstance();
        EnumTest instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

看結果:
在這里插入圖片描述
符合預期,所以使用列舉實作單例模式,是最安全和簡單的,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/255153.html

標籤:其他

上一篇:buuoj [第六章 CTF之PWN章]fsb

下一篇:3.3 執行緒安全分析

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more