線程間的共享和協(xié)作

java 支持多個線程同時訪問一個對象或者是對象的成員變量,關鍵字 synchroninzed 可以修飾方法或者以同步塊的形式來使用习勤,他主要確保多個線程在同一時刻,只能有一個線程處于處于方法或者是 同步塊中报亩,他保證了線程對變量訪問的可見性和排他性卷员,又稱為內(nèi)置鎖機制。

對象鎖和類鎖

對象鎖適用于對象實例方法居砖,或者一個對象上的虹脯,類鎖是用于類的靜態(tài)方法或者一個類的Class 對象上的。我們知道類的對象實例可以有多個奏候,但是每個類只有一個Class對象循集,所以不同對象的實例的對象鎖是互不干擾的,但是每個類只有一個類鎖蔗草。
有一點要注意:類鎖是一個概念上的東西咒彤,并不是真實存在的疆柔,類鎖其實鎖的是每個類的對應的class 對象。類鎖和對象鎖其實是互不干擾的镶柱。

線程間的協(xié)作

等待 / 通知機制

只能用在synchronized 包裹的塊中
等待:wait() ; 通知:notifyAll();
概念:一個線程A調(diào)用了B的wait()方法進入等待狀態(tài)旷档,而另一個線程C調(diào)用了B 的notify()或者是notifyAll()方法,線程A 收到了通知后從B的wait()返回歇拆,進而執(zhí)行后續(xù)的操作鞋屈。上述兩個線程通過對象B 來完成交互,而對象上的wait()方法和notify()方法的關系就如同開關一樣故觅,用來完成等待方和通知方之間的交互工作厂庇。
等待和通知的標準范式:

等待方遵循的條件

  • 獲取對象鎖
  • 如果條件不滿足,那么久就調(diào)用對象的wait()方法输吏,被通知后仍要檢查條件权旷。
  • 條件滿足執(zhí)行對應的邏輯


    等待方

    通知方遵循的條件:

  • 獲取對象鎖
  • 改變對象的條件
  • 通知所有等待在對象上的線程


    通知方

在通知的時候盡可能的去使用notifyAll()

代碼模塊:
等待方

/**
 * 快遞
 */
public class Express {
    public static final String CITY= "BeiJing";
    private int km; //運輸公里數(shù)
    private String site;  //到達地點

    public Express() {
    }

    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /**
     * 公里數(shù)變化了
     */
    public synchronized void changeKm(){
        this.km =110;
        notifyAll();
    }

    /**
     * 到達地點變化了
     */
    public  synchronized void changeSite(){
        this.site = "ShangHai";
        notifyAll();
    }

    public synchronized void waitKm(){
        while (km<100){
            try {
                wait();

                System.out.println("check Km thread"+Thread.currentThread().getName()+"is be notifyed");


            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("this is km "+km);
    }

    public synchronized void waitSite(){
        while (CITY.equals(site)){
            try {
                wait();

                System.out.println("check site thread"+Thread.currentThread().getName()+"is be notifyed");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("this is site " +site);
    }


}

通知方的代碼 包含測試代碼


/**
 * 測試wait nofify
 */
public class TextWn {

    private static Express express = new Express(0,Express.CITY);

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 3; i++) {
            new CheckKm().start();
        }

        for (int i = 0; i < 3; i++) {
            new CheckSite().start();
        }

        Thread.sleep(1000);
        express.changeKm();
        express.changeSite();


    }


    public static class CheckSite extends Thread {

        @Override
        public void run() {
            super.run();
            express.waitSite();

        }
    }

    public static class CheckKm extends Thread {

        @Override
        public void run() {
            super.run();
            express.waitKm();

        }
    }

}

以上可以直接運行。

ThreadLocal

使用static 的靜態(tài)變量评也,數(shù)據(jù)隔離
ThreadLoacl 是線程變量炼杖,是一個以ThreadLocal為鍵,任意對象為值的存儲結(jié)構(gòu)盗迟。這個結(jié)構(gòu)被附帶在線程上坤邪,也就是說一個線程可以根據(jù)一個ThreadLocal對象查詢到綁定在這個線程上的一個值,ThreadLocal往往用來實現(xiàn)變量在線程之間的隔離罚缕。
ThreadLocal 接口只有四個方法:

  • void set(Objcet value); 設置當前線程的局部變量的值
  • Object get(); 返回當前線程的局部變量的值
  • void remove(); 將當前線程局部變量的值刪除艇纺,減少內(nèi)存的占用。
  • Objcet initalValue(); 返回該線程局部變量的初始化值邮弹。

測試代碼:

public class UseThreadLocal {

    ThreadLocal<Integer> threadLocal = new ThreadLocal(){
        @Override
        protected Object initialValue() {
            return 1;
        }
    };

    public void startThreadT(){
        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 class TestThread implements Runnable{
        int id;
        public TestThread(int id){
            this.id = id;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            Integer s = threadLocal.get();
            s = s+id;
            threadLocal.set(s);
            System.out.println(Thread.currentThread().getName()+" :"
                    +threadLocal.get());
//            threadLocal.remove();
        }
    }

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

}

顯式鎖

Lock和synchronized 的比較
我們一般java 程序都是用synchronized 來進行加鎖的盟劫,使用synchronized 將會隱式的獲取鎖,但是它將鎖的獲取和釋放固化了与纽,也就是先獲取在釋放侣签,synchronized 是java 語言層面的鎖,也稱之為內(nèi)置鎖急迂。
synchronized 這種機制影所,一旦開始獲取鎖,是不能中斷的僚碎,也不提供嘗試獲取鎖的機制猴娩。

Lock 是有java 語法層面提供的,鎖的獲取和釋放是需要我們明顯的去操作,因此被成為顯式鎖卷中。并且提供了synchronized 不提供的機制矛双。


Lock 機制

Lock 的接口和核心方法
在finally 中釋放鎖,目的是獲取鎖以后能被釋放

       setLock.lock();
        try {
            //do....        
        }finally {
            setLock.unlock();
        }
lock 方法

可重入鎖ReentrantLock 仓坞、所謂鎖的公平與非公平
synchronized 關鍵字隱士的支持可重入背零,比如一個synchronized修飾的遞歸方法,在方法執(zhí)行時无埃,執(zhí)行線程在獲取了鎖以后仍能連續(xù)多次的獲的該鎖。ReentrantLock在調(diào)用lock()方法時毛雇,已經(jīng)獲取到了鎖的線程嫉称,能夠在調(diào)取lock()方法獲取所而不會阻塞。
公平和非公平鎖
如果在時間上灵疮,先對鎖進行獲取的請求一定先被滿足织阅,那么這個鎖是公平的,反之震捣,是不公平的荔棉。公平的獲取鎖,也就是等待時間最長的線程最優(yōu)先獲取鎖蒿赢,也可以說鎖獲取是順序的润樱。

ReentrantLock提供了一個構(gòu)造函數(shù),能夠控制鎖是否是公平的羡棵。事實上壹若,公平的鎖機制往往沒有非公平的效率高。原因是皂冰,在恢復一個被掛起的線程與該線程真正開始運行之間存在著嚴重的延遲店展。假設線程A持有一個鎖,并且線程B請求這個鎖。由于這個鎖已被線程A持有,因此B將被掛起秃流。當A釋放鎖時,B將被喚醒,因此會再次嘗試獲取鎖赂蕴。與此同時,如果C也請求這個鎖,那么C很可能會在B被完全喚醒之前獲得、使用以及釋放這個鎖舶胀。這樣的情況是一種“雙贏”的局面:B獲得鎖的時刻并沒有推遲,C更早地獲得了鎖,并且吞吐量也獲得了提高概说。

讀寫鎖 ReentrantReadWriteLock
之前提到鎖(synchronized和ReentrantLock)基本都是排他鎖,這些鎖在同一時刻只允許一個線程進行訪問峻贮,而讀寫鎖在同一時刻可以允許多個讀線程訪問席怪,但是在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞纤控。讀寫鎖維護了一對鎖挂捻,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖船万,使得并發(fā)性相比一般的排他鎖有了很大提升刻撒。
除了保證寫操作對讀操作的可見性以及并發(fā)性的提升之外骨田,讀寫鎖能夠簡化讀寫交互場景的編程方式。假設在程序中定義一個共享的用作緩存數(shù)據(jù)結(jié)構(gòu)声怔,它大部分時間提供讀服務(例如查詢和搜索)态贤,而寫操作占有的時間很少,但是寫操作完成之后的更新需要對后續(xù)的讀服務可見醋火。
一般情況下悠汽,讀寫鎖的性能都會比排它鎖好,因為大多數(shù)場景讀是多于寫的芥驳。在讀多于寫的情況下柿冲,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量.
代碼:

**
 * 商品類
 */
public class GoodsInfo {
    private final String name;
    private double totalMoney; //銷售總額
    private  int storeNumber;  //庫存數(shù)

    public GoodsInfo(String name, double totalMoney, int storeNumber) {
        this.name = name;
        this.totalMoney = totalMoney;
        this.storeNumber = storeNumber;
    }
    public double getTotalMoney() {
        return totalMoney;
    }

    public int getStoreNumber() {
        return storeNumber;
    }

    public void changeNumber(int sellNumber){
        this.totalMoney += sellNumber*25;
        this.storeNumber -= sellNumber;
    }
}

/**
 * 簡單的業(yè)務應用場景
 */
public interface GoodsService {

    public GoodsInfo getNum();//獲得商品的信息
    public void setNum(int number);//設置商品的數(shù)量
}

public class UseThread implements GoodsService{
    private GoodsInfo goodsInfo;
    private ReadWriteLock lock =new ReentrantReadWriteLock();
    private Lock rlock = lock.readLock();
    private Lock wlock = lock.writeLock();

    public UseThread(GoodsInfo goodsInfo) {
        this.goodsInfo = goodsInfo;
    }

    @Override
    public GoodsInfo getNum() {
        rlock.lock();
        try {
            try {
                TimeUnit.MILLISECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return this.goodsInfo;
        } finally {
            rlock.unlock();
        }

    }

    @Override
    public void setNum(int number) {
        wlock.lock();
        try {
            try {
                TimeUnit.MILLISECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            goodsInfo.changeNumber(number);
        }finally {
            wlock.unlock();
        }

    }
}
public class BusiApp {
    static final int readWriteRatio = 10;//讀寫線程的比例
    static final int minthreadCount = 3;//最少線程數(shù)

    //讀操作
    private static class GetThread implements Runnable {
        private GoodsService goodsService;
        public GetThread(GoodsService goodsService) {
            this.goodsService = goodsService;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for(int i=0;i<100;i++){//操作100次
                goodsService.getNum();
            }
            System.out.println(Thread.currentThread().getName()+"讀取商品數(shù)據(jù)耗時:"
                    +(System.currentTimeMillis()-start)+"ms");

        }
    }

    //寫操作
    private static class SetThread implements Runnable {
        private GoodsService goodsService;
        public SetThread(GoodsService goodsService) {
            this.goodsService = goodsService;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            Random r = new Random();
            for(int i=0;i<10;i++){//操作10次
                try {
                    TimeUnit.MILLISECONDS.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                goodsService.setNum(r.nextInt(10));
            }
            System.out.println(Thread.currentThread().getName()
                    +"寫商品數(shù)據(jù)耗時:"+(System.currentTimeMillis()-start)+"ms---------");

        }

    }

    public static void main(String[] args) {
        GoodsInfo goodsInfo = new GoodsInfo("Cup",100000,10000);
        GoodsService goodsService = new UseThread(goodsInfo);
        for(int i = 0;i<minthreadCount;i++){
            Thread setT = new Thread(new SetThread(goodsService));
            for(int j=0;j<readWriteRatio;j++) {
                Thread getT = new Thread(new GetThread(goodsService));
                getT.start();
            }
            try {
                TimeUnit.MILLISECONDS.sleep(100);
                setT.start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

Condition接口

任意一個Java對象,都擁有一組監(jiān)視器方法(定義在java.lang.Object上)兆旬,主要包括wait()假抄、wait(long timeout)、notify()以及notifyAll()方法丽猬,這些方法與synchronized同步關鍵字配合宿饱,可以實現(xiàn)等待/通知模式。Condition接口也提供了類似Object的監(jiān)視器方法脚祟,與Lock配合可以實現(xiàn)等待/通知模式谬以。
用Lock和Condition實現(xiàn)等待通知

Condition

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市愚铡,隨后出現(xiàn)的幾起案子蛉签,更是在濱河造成了極大的恐慌,老刑警劉巖沥寥,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碍舍,死亡現(xiàn)場離奇詭異,居然都是意外死亡邑雅,警方通過查閱死者的電腦和手機片橡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淮野,“玉大人捧书,你說我怎么就攤上這事≈栊牵” “怎么了经瓷?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洞难。 經(jīng)常有香客問我舆吮,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任色冀,我火速辦了婚禮潭袱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锋恬。我一直安慰自己屯换,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布与学。 她就那樣靜靜地躺著彤悔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪癣防。 梳的紋絲不亂的頭發(fā)上蜗巧,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音蕾盯,去河邊找鬼。 笑死蓝丙,一個胖子當著我的面吹牛级遭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渺尘,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼挫鸽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸥跟?” 一聲冷哼從身側(cè)響起丢郊,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎医咨,沒想到半個月后枫匾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡拟淮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年干茉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片很泊。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡角虫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出委造,到底是詐尸還是另有隱情戳鹅,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布昏兆,位于F島的核電站枫虏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜模软,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一伟骨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧燃异,春花似錦携狭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仅颇,卻和暖如春单默,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背忘瓦。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工搁廓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耕皮。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓境蜕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凌停。 傳聞我的和親對象是個殘疾皇子粱年,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容