Java併發編程-基礎篇

1. JVM與線程安全

可見性:當多個線程對一個線程進行操作的時候,其中一個線程修改了變量的值,而其他的線程並不知道該值已經被修改
可見性-synchronized
JMM關於synchronized的兩條規定:
1、線程解鎖前,必須把共享變量的最新值刷新到主內存
2、線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值(注意:加鎖與解鎖是同一把鎖)
可見性-volatile
通過加入內存屏障和禁止重排序優化來實現
1、對volatile變量寫操作時,會在寫操作後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內存
2、對volatile變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存讀取共享變量

原子性:修改操作不可被切割

2. synchronized關鍵字原理

線程安全概念:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,
就是線程安全的。
線程安全問題都是又全局變量以及靜態變量引起的。

如果每個線程對全局變量、靜態變量只有讀操作,沒有寫操作,一般來說是線程安全的。如果多個線程同時執行寫操作,一般需要考慮線程同步,否則的話可能會影響線程安全。

synchronized作用:加鎖,所有synchronize方法都會順序執行

執行方式:

  1. 嘗試獲取鎖
  2. 如果獲得了鎖,執行synchronized的方法體的內容
  3. 如果無法獲得鎖,則不斷的嘗試獲得鎖。一旦鎖被釋放,則多個線程會同時嘗試獲得鎖,造成鎖競爭的問題。【鎖競爭在高併發、線程數量高是會引發CPU佔用居高不下,甚至直接宕機】

3. 對象鎖和類鎖

synchronized作用在非靜態方法上代表的是對象鎖,一個對象一個鎖,多個對象之間不會發生鎖競爭
synchronized作用在靜態方法時,則升級爲類鎖,所有該類的對象共享一把鎖,存在鎖競爭。

4. 對象鎖的同步和異步

同一個對象中所有的synchronized方法都是同步執行的。非synchronized方法異步執行。
synchronized加鎖的最小粒度是對象。
例如:如果一個對象中有兩個synchronized方法func1和func2,兩個線程分別對應func1和func2,這兩個方法之間也會存在鎖競爭。

5. 髒讀問題

多個線程訪問同一個資源,在一個線程修改數據的過程中,有另外一個線程來讀取數據,就會引起髒讀。
爲了避免髒讀,在操作時要保證數據修改操作的原子性,並且對讀操作也要進行同步控制

6.鎖重入

同一個線程得到了一個對象鎖之後,再次請求此對象是可以再次獲取該對象的鎖。
同一個對象的多個synchronized方法可以重入

synchronized A()在調用synchronized B()方法時, A與B不會存在鎖競爭,而是存在鎖重入。
父子類的鎖可以重入

7. 拋出異常釋放鎖

一個線程在獲得鎖之後執行操作,如果發生錯誤拋出異常,則走自動釋放鎖。

  1. 可以利用拋出異常,主動釋放鎖
  2. 程序異常時,防止資源被死鎖,無法釋放
  3. 異常釋放鎖可能導致數據不一致

8. synchronized代碼塊

相同類型的鎖互斥,不同類型的鎖互不干擾。

如果在線程內修改了鎖的引用,則會導致鎖失效。
修改鎖對象的屬性不會導致鎖失效,修改鎖對象的引用會導致鎖失效。

final關鍵字的含義?
final在Java中是一個保留的關鍵字,可以聲明成員變量、方法、類以及本地變量。一旦你將引用聲明作final,你將不能改變這個引用了,編譯器會檢查代碼,如果你試圖將變量再次初始化的話,編譯器會報編譯錯誤。

修改鎖引用導致鎖失效的DEMO

package com.jimmy.test;

/**
 * 修改鎖的引用導致鎖失效
 */
public class Test {
    private String lock = "object lock";

    private void method() {
        synchronized (lock) {
            try {
                System.out.println("start - " + Thread.currentThread().getName()  + " use " + lock);
                // 如果在這裏修改了鎖的引用,則鎖會失效
                lock = "changed object lock";
                Thread.sleep(2000);
                System.out.println("end - " + Thread.currentThread().getName()  + " use " + lock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final Test test = new Test();
        Thread t1 = new Thread(() -> test.method(), "t1");
        Thread t2 = new Thread(() -> test.method(), "t2");

        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

鎖不失效的結果:
在這裏插入圖片描述
鎖失效的結果:
在這裏插入圖片描述
在聲明鎖的時候使用final關鍵字,可以避免鎖被修改
在這裏插入圖片描述

9. 死鎖

死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

10. 線程之間的通信

object類中的wait/notify 可以實現線程通信
wait/notify必須要synchronized關鍵字一起使用

wait 釋放鎖
notify 不釋放鎖,只發出通知

實例:

TODO 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章