執行緒安全(三個條件)Synchronzied,wait和notify

小藝小藝發表於2021-01-01

原子性

記憶體可見性

程式碼重排序

執行緒不安全:執行結果和預期結果之間出現概率性
概率性的來源:執行緒切換的隨機性
經過三個條件的放大:原子性/記憶體可見性/程式碼重排序

程式碼實踐

ArrayList

如何設計程式碼,進而使得程式碼具備執行緒安全問題

思考:

  1. 執行緒安全是什麼?
  2. 隨機性來自何處?
  3. 隨機性如何傳遞出來?
  4. JVM記憶體區域劃分的共享和私有?
    實踐:
    a) 哪些情況需要寫多執行緒程式碼?
    b) 寫了多執行緒之後,哪裡出現執行緒安全的風險了?
    c) 如何對風險進行保護?

鎖機制
原子性+可見性+程式碼重排序起到一定的保護

Synchronzied關鍵字

Synchronzied關鍵子的作用

Volatile的不穩定機制

Volatile Person p;
P = new Person(…); 可以保證一定是1->2->3的順序

單例模式

阻塞佇列

執行緒通訊

Wait如果有多個,notify會喚醒隨機的一個執行緒(不保證哪一個)
Notifyall把所有的都叫醒

public class 獲取反射類引用 {
static class Person {}

public static void main(String[] args) {
    Person p1 = new Person();
    Person p2 = new Person();

    // 通過物件引用獲取反射類物件引用
    Class<? extends Person> c1 = p1.getClass();
    Class<? extends Person> c2 = p2.getClass();

    // 通過類名獲取反射類物件引用
    Class<Person> c3 = Person.class;

    System.out.println(c1 == c2);
    System.out.println(c1 == c3);
}

}

public class 修復之前的執行緒安全問題 {
private static final int COUNT = 100_0000;
private static int n = 0;
private static Object lock = new Object();

static class Adder extends Thread {
    @Override
    public void run() {
        synchronized (lock) {
            for (int i = 0; i < COUNT; i++) {
                n++;
            }
        }
    }
}

static class Suber extends Thread {
    @Override
    public void run() {
        synchronized (lock) {
            for (int i = 0; i < COUNT; i++) {
                n--;
            }
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Adder();
    Thread t2 = new Suber();
    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println(n);
}

}

public class Synchronized的語法使用示例 {
// 同步方法
synchronized int add(int a, int b) {
return 0;
}

synchronized static void sayHello() {
}

// 同步程式碼塊 —— 能出現語句的地方
static void someMethod() {
    Object object = new Object();
    synchronized (object) {

    }
}

}

package 單例模型;

// 執行緒安全 + 簡單
// 構造的早,如果不用,就浪費空間了
public class Hungry {
private Hungry() {}

private static Hungry instance = new Hungry();  // 只會有這麼一個物件

public static Hungry getInstance() {
    return instance;
}

}

package 單例模型;

// 不是執行緒安全的
public class LazyVersion1 {
private static LazyVersion1 instance = null;

public static LazyVersion1 getInstance() {
    // 有人要用到該物件了
    if (instance == null) {
        // 第一次的時候,進行例項化,以後不再進行
        instance = new LazyVersion1();
    }

    return instance;
}

}

package 單例模型;

// 執行緒安全了
// 效能不高
public class LazyVersion2 {
private static LazyVersion2 instance = null;

public static LazyVersion2 getInstance() {
    synchronized (LazyVersion2.class) {
        if (instance == null) {
            instance = new LazyVersion2();
        }
    }

    return instance;
}

}

package 單例模型;

// 1. 為什麼要二次判斷
// 2. 效能是怎麼提高的
// 3. instance = new LazyVersion3() 可能會被重排序成 1 -> 3 -> 2
public class LazyVersion3 {
private static LazyVersion3 instance = null;

public static LazyVersion3 getInstance() {
    if (instance == null) {
        // 100年的1秒
        synchronized (LazyVersion3.class) {
            // 能 保證 instance 還是 null
            if (instance == null) {
                instance = new LazyVersion3();
            }
        }
    }

    return instance;
}

}

package 單例模型;

// 1. 為什麼要二次判斷
// 2. 效能是怎麼提高的
public class LazyVersion4 {
// volatile 的目的:
// 保護 instance = new LazyVersion4(); 一定是 1 -> 2 -> 3 的順序
// 而不至於出現,其他執行緒看到 instance != null,但執行的是一個沒有被初始化完的物件。
private static volatile LazyVersion4 instance = null;

public static LazyVersion4 getInstance() {
    if (instance == null) {
        // 100年的1秒
        synchronized (LazyVersion4.class) {
            // 能 保證 instance 還是 null
            if (instance == null) {
                instance = new LazyVersion4();
            }
        }
    }

    return instance;
}

}

相關文章