2020-11-02--線程與并發(fā)

1.死鎖
1.1產(chǎn)生死鎖的四個必要條件
互斥條件:一個線程對獲取到的資源具有排他性,及一個資源只能被一個線程所占用。
請求與保持:一個線程因請求被占有的資源發(fā)生阻塞的時候,不會釋放自己所占有的資源。
不剝奪條件:自己占有的資源不能被其他線程所剝奪,只有自己資源運行完成后才能被釋放。
循環(huán)與等待:發(fā)生死鎖的時候一定會形成一個閉環(huán)。
1.2解決死鎖:
破壞四個條件任意一個
破壞互斥:不可以它是線程的性質(zhì)
破壞請求與保持:一次性占有所有的資源。
破壞不可剝奪:當(dāng)一個線程申請資源申請不到時釋放自己所占有的資源。
破壞循環(huán)與等待:一個線程按一定順序獲取資源,然后按照反序來釋放資源。

產(chǎn)生死鎖的例子:

public class bingfa {
    private   static Object object1=new Object();
    private   static Object object2=new Object();

    public static void main(String[] args) {
        new Thread(() ->{
            synchronized (object1){
                System.out.println(Thread.currentThread().getName()+"獲取到了資源一");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程一等待一秒鐘后");
                synchronized (object2){
                    System.out.println(Thread.currentThread().getName()+"獲取到了資源二");
                }
            }
        }).start();
        new Thread(() ->{
            synchronized (object2){
                System.out.println(Thread.currentThread().getName()+"獲取到了資源一");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程二等待一秒鐘后");
                synchronized (object1){
                    System.out.println(Thread.currentThread().getName()+"獲取到了資源二");
                }
            }
        }).start();
    }
}
image.png

上面代碼結(jié)果可以看到線程一獲取不到資源二、線程二獲取不到資源一。
通過破壞循環(huán)等待來防止死鎖及通過調(diào)整順序來防止死鎖。

public class bingfa {
    private   static Object object1=new Object();
    private   static Object object2=new Object();

    public static void main(String[] args) {
        new Thread(() ->{
            synchronized (object1){
                System.out.println(Thread.currentThread().getName()+"獲取到了資源一");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程一等待一秒鐘后");
                synchronized (object2){
                    System.out.println(Thread.currentThread().getName()+"獲取到了資源二");
                }
            }
        }).start();
        new Thread(() ->{
            synchronized (object1){
                System.out.println(Thread.currentThread().getName()+"獲取到了資源二");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程二等待一秒鐘后");
                synchronized (object2){
                    System.out.println(Thread.currentThread().getName()+"獲取到了資源二");
                }
            }
        }).start();
    }
}
image.png

悲觀鎖:
總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現(xiàn)。
樂觀鎖:
總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號機制和CAS算法實現(xiàn)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫提供的類似于write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現(xiàn)方式CAS實現(xiàn)的。

3.volatile
3.1它時用來保證線程安全的,但是他只保證了程序的可見性不能保證變量的原子性。
原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。

public class valititelTest {
    private volatile int  a = 0;
    volatile boolean flag = false;

    public void write() {
        a = 1;
        flag = true;
        System.out.println("write over");
    }
    public  void  read(){
        if (flag){
            System.out.println("i is"+a);
            System.out.println("read true");
        }

        else{
            System.out.println("i is"+a);
            System.out.println("read false");
        }
    }

    public static void main(String[] args) {
        valititelTest a=new valititelTest();
        new Thread(() -> a.write()).start();
        new Thread(() -> a.read()).start();
    }

}

image.png

從結(jié)果可以看出,一個線程對volitatel變量進行操作另外一個線程能夠看到它狀態(tài)的改變,也允許它的改變。

4.csa
轉(zhuǎn)載介紹csa的博客
[https://blog.csdn.net/qq_32998153/article/details/79529704]
4.1、簡介:CAS 操作包含三個操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存地址里面的值和 A 的值是一樣的,那么就將內(nèi)存里面的值更新成 B。CAS是通過無限循環(huán)來獲取數(shù)據(jù)的,若果在第一輪循環(huán)中,a 線程獲取地址里面的值被b 線程修改了,那么 a 線程需要自旋,到下次循環(huán)才有可能機會執(zhí)行。
4.2、ABA問題:銀行轉(zhuǎn)賬的例子,小陳銀行賬戶有100塊錢,取出50,發(fā)起了兩次、小陳老媽存50.?,F(xiàn)在的情況是小陳中一個線程取錢取成公了,小陳老媽存成功之后,另外一個線程發(fā)現(xiàn)100然后取出50,現(xiàn)在賬戶是50塊錢。

5.threadlocald
應(yīng)用場景:變量是同一個,但是每個線程都使用同一個初始值,也就是使用同一個變量的一個新的副本。這種情況之下ThreadLocal就非常使用。每個Thread對象內(nèi)部都維護了一個ThreadLocalMap這樣一個ThreadLocal的Map,可以存放若干個ThreadLocal。當(dāng)我們在調(diào)用get()方法的時候,先獲取當(dāng)前線程,然后獲取到當(dāng)前線程的ThreadLocalMap對象,如果非空,那么取出ThreadLocal的value,否則進行初始化,初始化就是將initialValue的值set到ThreadLocal中。
應(yīng)用實例:jdbc數(shù)據(jù)庫連接,session管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容