筆記:多線程并發(fā)編程(1)鎖、ThreadLocal、同步機制使用

死鎖

是指兩個或兩個以上的進程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖。

產(chǎn)生死鎖的條件:

1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內(nèi)某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。
2)請求和保持條件:指進程已經(jīng)保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。
3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。
4)環(huán)路等待條件:指在發(fā)生死鎖時,必然存在一個進程——資源的環(huán)形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。

  • 自己總結(jié)(人話):
    1. 爭奪者數(shù)目大于爭奪資源
    2. 爭奪資源順序不對
    3. 拿到資源不放手
    4. 有另外一個等待使用資源的線程
解決死鎖:

只要打破四個必要條件之一就能有效預(yù)防死鎖的發(fā)生。
打破互斥條件:改造獨占性資源為虛擬資源,大部分資源已無法改造。
打破不可搶占條件:當(dāng)一進程占有一獨占性資源后又申請一獨占性資源而無法滿足,則退出原占有的資源。
打破占有且申請條件:采用資源預(yù)先分配策略,即進程運行前申請全部資源,滿足則運行,不然就等待,這樣就不會占有且申請。
打破循環(huán)等待條件:實現(xiàn)資源有序分配策略,對所有設(shè)備實現(xiàn)分類編號,所有進程只能采用按序號遞增的形式申請資源。

  • 自己總結(jié)(人話):
    1. 確定每個線程的拿鎖順序
    2. 采用嘗試拿鎖的方式
活鎖

頻繁申請鎖兩個線程在嘗試拿鎖的機制中,發(fā)生多個線程之間互相謙讓,不斷發(fā)生同一個線程總是拿到同一把鎖,在嘗試拿另一把鎖時因為拿不到,而將本來已經(jīng)持有的鎖釋放的過程。

解決辦法:每個線程休眠隨機數(shù),錯開拿鎖的時間。

線程饑餓

低優(yōu)先級的線程,總是拿不到執(zhí)行時間

Thread Local

Thread Local 線程本地變量 為每個線程提供一個變量副本。實現(xiàn)線程隔離
eg: 創(chuàng)建三個線程分別對變量count+線程id

  • 未使用ThreadLocal:
public class NoThreadLocal {
    static Integer count = new Integer(1);
    /**
     * 運行3個線程
     */
    public void startTArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new TestTask(i));
        }
        for(int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }
    /**
     *類說明:
     */
    public static class TestTask implements Runnable{
        int id;
        public TestTask(int id){
            this.id = id;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            count = count+id;
            System.out.println(Thread.currentThread().getName()+":"
                    +count);
        }
    }

    public static void main(String[] args){
        NoThreadLocal test = new NoThreadLocal();
        test.startTArray();
    }
}

打印結(jié)果:
Thread-1:start
Thread-1:2
Thread-0:start
Thread-0:2
Thread-2:start
Thread-2:4

并沒有每個線程按照 t0->0 、t1->1 、t2->2

  • 使用ThreadLocal:
public class UseThreadLocal {

    //TODO
   private static ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>(){
        int count = 0;
        @Override
        protected Integer initialValue() {
            return count;
        }
    };
    /**
     * 運行3個線程
     */
    public void StartThreadArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new TestThread(i));
        }
        for(int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }
    
    /**
     *類說明:測試線程,線程的工作是將ThreadLocal變量的值變化,并寫回,看看線程之間是否會互相影響
     */
    public static class TestThread implements Runnable{
        int id;
        public TestThread(int id){
            this.id = id;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            //TODO
            int value = intThreadLocal.get()+id;
            intThreadLocal.set(value);
            System.out.println(Thread.currentThread().getName()+" : "+intThreadLocal.get());
        }
    }

    public static void main(String[] args){
        UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}
打印結(jié)果:
Thread-0:start
Thread-0 : 0
Thread-1:start
Thread-1 : 1
Thread-2:start
Thread-2 : 2
  • ThreadLocal簡析:
    前面說到ThreadLocal是本地變量副本。那么他是怎么實現(xiàn)的呢。由ThreadLocal的set()入手
ThreadLocal.java
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//通過線程獲得ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
拿到 ThreadLocal.ThreadLocalMap threadLocals
可以看到ThreadLocal.ThreadLocalMap 里面有個Entity[]
Entity的具體模型:
 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            //以threadLocal為key value 為值存入
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
因為可能存在多個類型的threadlocal 所以需要使用數(shù)組
threadLocal解析.png
不同線程內(nèi)部ThreadLocal解析.png

volatile的使用 (最輕量的同步機制)

適合一寫多讀的場景

  • 優(yōu)點:保證可見性與快速更新
  • 缺點:無法保證線程安全與操作原子性
    使用 :
  • 可見性例子:
public class VolatileCase {
    private  static boolean ready;
    private static int number;

    private static class PrintThread extends Thread{
        @Override
        public void run() {
            System.out.println("PrintThread is running.......");
            while(!ready);
            System.out.println("number = "+number);
        }
    }

    public static void main(String[] args) {
        new PrintThread().start();
        SleepTools.second(1);
        number = 51;
        ready = true;
        SleepTools.second(5);
        System.out.println("main is ended!");
    }
}
打印結(jié)果:
PrintThread is running.......
main is ended!
PrintThread并不知曉ready已經(jīng)變化

加入 volatile關(guān)鍵字
private  volatile  static boolean ready;
打印結(jié)果:
PrintThread is running.......
number = 51
main is ended!

線程不安全例子:

public class NotSafe {
    private volatile long count =0;

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    //count進行累加
    public void incCount(){
        count++;
    }

    //線程
    private static class Count extends Thread{

        private NotSafe simplOper;

        public Count(NotSafe simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                simplOper.incCount();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NotSafe simplOper = new NotSafe();
        //啟動兩個線程
        Count count1 = new Count(simplOper);
        Count count2 = new Count(simplOper);
        count1.start();
        count2.start();
        Thread.sleep(50);
        System.out.println(simplOper.count);
    }
}
理想結(jié)果:20000
打印結(jié)果:
20000
13671
13529
因為線程的執(zhí)行是需要有cpu執(zhí)行權(quán)的 所以導(dǎo)致了結(jié)果的不確定性

synchronized的使用

synchronized 一定是作用在某個對象上 當(dāng)所在static 的方法 或者靜態(tài)塊 時 鎖住的是 X.class的對象
注:鎖只有作用在同個對象上才會起作用

public class SynTest {

    private long count =0;

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    /*用在同步塊上*/
    public void incCount(){
            count++;
    }

    

    //線程
    private static class Count extends Thread{

        private SynTest simplOper;

        public Count(SynTest simplOper) {
            this.simplOper = simplOper;
        }

        @Override
        public void run() {
            for(int i=0;i<10000;i++){
                simplOper.incCount();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynTest simplOper = new SynTest();
        //啟動兩個線程
        Count count1 = new Count(simplOper);
        Count count2 = new Count(simplOper);
        count1.start();
        count2.start();
        Thread.sleep(50);
        System.out.println(simplOper.count);//20000
    }
}
打印結(jié)果:
18748 20000 16735 18307 
加鎖:
public void incCount(){
        synchronized (obj){
            count++;
        }
    }
打印結(jié)果:
20000 20000 20000 20000

鎖的作用對象:

public class SynTest {

    private long count =0;
    private Object obj = new Object();//作為一個鎖

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

        /*鎖的是SyncTest.class的類對象*/
    public void incCount(){
        synchronized (SyncTest.class){
            count++;
        }
    }

    /*用在方法上 鎖的是obj對象*/
    public void incCount(){
        synchronized (obj){
            count++;
        }
    }

      /*用在方法上 鎖的也是SynTest.class對象*/
    public static synchronized void incCount2(){
            count++;
    }

    /*用在方法上 鎖的也是當(dāng)前對象實例*/
    public synchronized void incCount2(){
            count++;
    }

    /*用在同步塊上,但是鎖的是當(dāng)前類的對象實例*/
    public void incCount3(){
        synchronized (this){
            count++;
        }
    }
}

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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