多線程筆記

1. volatile

1.1 volatile介紹

volatile保證了共享變量的“可見性”去件∶咭可見性的意思是當一個線程修改一個共享變量時必孤,另外一個線程能讀到這個修改的值蓖救。它在某些情況下比synchronized的開銷更小侣肄。

舉個例子我們來分析下面的代碼:

public class Main {
    public static void main(String[] args) {
        VolatileTest volatileTest = new VolatileTest();
        new Thread(volatileTest).start();

        while (true) {
            if (volatileTest.isFlag()){
                System.out.println("over");
                break;
            }
        }
    }
}

class VolatileTest implements Runnable {
    private boolean flag;

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag=" + flag);
    }

    public boolean isFlag() {
        return flag;
    }
}

上面的代碼最后輸出結(jié)果是:

flag=true

這個結(jié)果是令人詫異的旧困,程序會一直執(zhí)行while循環(huán)不結(jié)束,flag已經(jīng)為true了稼锅,為什么while循環(huán)還是不結(jié)束呢吼具?說明這里的flag同時有了兩個值

  • 在主線程中:flag=false
  • 在副線程中:flag=true

變量實際是一段內(nèi)存空間,并不存在同時有兩種信息的狀態(tài)矩距。其實線程在操作主存中的變量數(shù)據(jù)時拗盒,首先會將數(shù)據(jù)復制到線程私有內(nèi)存中,當操作完成后才會將數(shù)據(jù)寫回主存锥债,當多個線程操作一個共享變量時陡蝇,由于線程的修改痊臭,導致數(shù)據(jù)不一致性。就發(fā)生了上述結(jié)果登夫。

為了解決共享變量的不一致性广匙,使得多線程對共享變量的修改的可見。上述結(jié)果我們可以使用synchronized關(guān)鍵字來解決恼策。如下:

 while (true) {
            synchronized (volatileTest){
                if (volatileTest.isFlag()){
                    System.out.println("over");
                    break;
                }
            }
        }

當是這樣解決又有一個很大的問題鸦致,synchronized是悲觀鎖,使得多線程堵塞等待戏蔑,極大的降低多線程的效率蹋凝。那有沒有一個更好的解決辦法呢?這里就可以用到volatile關(guān)鍵字了总棵,修改如下:

    private volatile boolean flag;

只需要將變量flag聲明時鳍寂,用volatile修飾就可以保證共享變量flag的可見性。再次運行就不會發(fā)生堵塞數(shù)據(jù)不一致的問題了情龄。

注意 : 如果將代碼改成下面的迄汛,運行結(jié)果也是沒有問題的,導致上面的結(jié)果還有一個重要的原因骤视,while循環(huán)中執(zhí)行的太快鞍爱,導致主線程來不及去主存中刷新數(shù)據(jù)。

        while (true) {
            // 只要是需要消耗一定的時間专酗,讓主線程能從主存讀取數(shù)據(jù)即可
            System.out.println("no over"); 
            if (volatileTest.isFlag()) {
                System.out.println("over");
                break;
            }
        }

1.2 volatile的三大特性:

  1. 可見性
  2. 不保證原子性
  3. 禁止指令重排

具體是如何做到的可以參考以下博客
死磕Java——volatile的理解

2. Atomic

jdk1.5java.util.concurrent.atomic包下提供了常用的原子操作類睹逃,什么是原子操作呢?顧名思義祷肯,就是不可分割的操作沉填。

  1. i++的原子性問題:i++的操作實際上分為三個步驟"讀-改-寫"
  int i=10;
  i=i++; //10
// 上面的代碼等同于下面的
  int i = 10;
  int temp=i;
  i=i+1;
  i=temp;
// 所以最后i的值為10
  1. 原子變量:jdk1.5后java.util.concurrent.atomic包下提供了常用的原子變量:


    java.util.concurrent.atomic包
  • volatile 保證內(nèi)存可見性
  • CAS(Compare-And-Swap)算法保證數(shù)據(jù)的原子性CAS算法是硬件對于并發(fā)操作共享數(shù)據(jù)的支持
    CAS包含了三個操作數(shù):
    • 內(nèi)存值V
    • 預估值A
    • 更新值B
      當且僅當V==A時,V = B佑笋,否則將不做任何操作

可參考博客:
Java中atomic包中的原子操作類總結(jié)

CAS的實現(xiàn)需要硬件指令集的支撐翼闹,在JDK1.5后虛擬機才可以使用處理器提供的CMPXCHG指令實現(xiàn)。

3. ConcurrentHashMap

3.1 ConcurrentHashMap 采用"鎖分段"機制

Java5.0java.util.concurrent包中提供了多種并發(fā)容器類來改進同步容器的性能蒋纬。ConcurrentHashMap同步容器類是Java5增加的一個線程安全的哈希表猎荠。對與多線程的操作,介于HashMapHashtable之間蜀备。內(nèi)部采用“鎖分段”機制替代Hashtable的獨占鎖关摇。進而提高性能。此包還提供了設(shè)計用于多線程上下文中的Collection實現(xiàn):
ConcurrentHashMap碾阁、ConcurrentSkipListMap输虱、ConcurrentSkipListSetCopyOnWriteArrayListCopyOnWriteArrayset瓷蛙。當期望許多線程訪問一個給定collection時悼瓮,ConcurrentHashMap通常優(yōu)于同步的HashMap戈毒,ConcurrentSkipListMap通常優(yōu)于同步的TreeMap。當期望的讀數(shù)和遍歷遠遠大于列表的更新數(shù)時横堡,CopyOnWriteArrayList優(yōu)于同步的ArrayList

4. CountDownLatch

4.1 CountDownLatch閉鎖

CountDownLatch一個同步輔助類埋市,在完成一組正在其他線程中執(zhí)行的操作之前,它允許一個或多個線程一直等待命贴。
閉鎖可以延遲線程的進度直到其到達終止狀態(tài)道宅,閉鎖可以用來確保某些活動直到其他活動都完成才繼續(xù)執(zhí)行:

  • 確保某個計算在其需要的所有資源都被初始化之后才繼續(xù)執(zhí)行;
  • 確保某個服務在其依賴的所有其他服務都已經(jīng)啟動之后才啟動胸蛛;
  • 等待直到某個操作所有參與者都準備就緒再繼續(xù)執(zhí)行污茵。

CountDownLatch使用實例代碼:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);
        LatchDemo ld = new LatchDemo(latch);
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10; i++) {
            new Thread(ld).start();
        }

        latch.await();
        long end = System.currentTimeMillis();

        System.out.println("總時長為:" + (end - start));
    }
}

class LatchDemo implements Runnable {

    private CountDownLatch latch;

    public LatchDemo(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            double random = Math.random() * 100;
            if (random > 99) {
                System.out.println(random);
            }
        }
        latch.countDown();
    }
}

Callable

Callable介紹

Runnable是執(zhí)行工作的獨立任務,但是它不返回任何值葬项。在Java SE5中引入的Callable是一種具有類型參數(shù)的泛型泞当,泛型類型是方法call()的返回的值類型。

四種執(zhí)行線程方式的介紹

種數(shù) 種類 說明
1 實現(xiàn)Runnable接口 通過Thread實例啟動它
2 繼承Thread 重寫Threadrun方法
3 實現(xiàn)Callable接口 通過FutureTask包裝民珍,然后再通過Thread啟動
4 實現(xiàn)Callable接口 ExecutorServices.submit()

可參考博客:
徹底理解Java的Future模式
Future模式添加Callback及Promise 模式

Lock

用于解決多線程安全問題的方式:

  • synchronized:隱式鎖襟士、重量級

    1. 同步代碼塊
    2. 同步方法
  • jdk 1.5后,Lock:輕量級

    1. 同步鎖Lock
      注意:是一個顯示鎖嚷量,需要通過lock()方法上鎖陋桂,必須通過unlock()方法進行釋放鎖

多線程安全問題演示

買票案例代碼演示:

public class Main {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket,"一號售票窗口").start();
        new Thread(ticket,"二號售票窗口").start();
        new Thread(ticket,"三號售票窗口").start();
    }
}

class Ticket implements Runnable {

    private int num = 100;

    @Override
    public void run() {
        while (true) {
            if (num > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("賣出一張票,剩余還有:" + --num);
            }else if (num == 0){
                break;
            }
        }
    }
}

上面的代碼存在線程安全問題 蝶溶,多線程下對同一共享變量進行修改嗜历。
用第一種保證安全性:

while (true) {
            synchronized (this){
                if (num > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("賣出一張票,剩余還有:" + --num);
                }else if (num == 0){
                    break;
                }
            }
        }

但是這樣效率嚴重降低抖所。
用第三種方式保證安全性:

while (true) {
            lock.lock();
            try{
                if (num > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("賣出一張票梨州,剩余還有:" + --num);
                } else if (num == 0) {
                    break;
                }
            }finally {
                lock.unlock();
            }
        }

Condition

編寫一個程序,開啟3個線程部蛇,這三個線程的ID分別為A摊唇、B咐蝇、C涯鲁,每個線程將自己的ID在屏幕上打印10遍,要求輸出的結(jié)果必須按順序顯示有序。
如:ABCABCABC….依次遞歸

public class Main {
    public static void main(String[] args) {
        Test test = new Test();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                test.LoopA();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                test.LoopB();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                test.LoopC();
            }
        },"C").start();
    }
}

class Test {

    private int id = 1;
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void LoopA() {
        lock.lock();
        try {
            while (id != 1) {
                condition1.await();
            }
            System.out.println("A");
            id = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void LoopB() {
        lock.lock();
        try {
            while (id != 2) {
                condition2.await();
            }
            System.out.println("B");
            id = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void LoopC() {
        lock.lock();
        try {
            while (id != 3) {
                condition3.await();
            }
            System.out.println("C");
            id = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

ReadWriteLock 讀寫鎖

寫寫/讀寫 需要“互斥”
讀讀 不需要互斥

public class Main {
    public static void main(String[] args) {
        Test test = new Test();
        for (int i = 0; i < 10; i++) {
            int j = i;
            new Thread(() -> {
                test.write("" + j, new Random().nextInt(10));
            }).start();
        }
        for (int i = 0; i < 100; i++) {
            int j = i;
            new Thread(() -> {
                test.read("" + j);
            }).start();
        }
    }
}

class Test {

    private int id = 1;
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public void read(String name) {
        rwl.readLock().lock();
        try {
            System.out.println(String.format("名字:%s抹腿,讀出的數(shù)據(jù):%d", name, id));
        } finally {
            rwl.readLock().unlock();
        }
    }

    public void write(String name, int id) {
        rwl.writeLock().lock();
        try {
            this.id = id;
            System.out.println(String.format("我是寫鎖,名字:%s旭寿,改寫數(shù)據(jù)為:%d", name, id));
        } finally {
            rwl.writeLock().unlock();
        }
    }
}

線程八鎖

  1. 兩個普通同步方法警绩,兩個線程,標準打印盅称,打蛹缦椤后室?//one two
  2. 新增 Thread.sleep()給getone(),打踊旌荨岸霹?//one two
  3. 新增普通方法 getThree(),打咏取贡避?//three one two
  4. 兩個普通同步方法,兩個Number對象予弧,打庸伟伞?//two one
  5. 修改 getone()為靜態(tài)同步方法掖蛤,打由蹦怼?//two one
  6. 修改兩個方法均為靜態(tài)同步方法蚓庭,一個Number對象水醋?//one two
  7. 一個靜態(tài)同步方法,一個非靜態(tài)同步方法彪置,兩個Number對象拄踪?//two one
  8. 兩個靜態(tài)同步方法,兩個Number對象拳魁?//one two

線程八鎖的關(guān)鍵:

  • 非靜態(tài)方法的鎖默認為this惶桐,靜態(tài)方法的鎖為對應的Class實例
  • 某一個時刻內(nèi),只能有一個線程持有鎖潘懊,無論幾個方法姚糊。

線程池

線程池介紹

線程池:提供了一個線程隊列,隊列中保存著所有等待狀態(tài)的線程授舟。避免了創(chuàng)建與銷毀額外開銷救恨,提高了響應的速度。

線程池的體系結(jié)構(gòu):

java.util.concurrent.Executor:負責線程的使用與調(diào)度的根接口

  |--**ExecutorService子接口:線程池的主要接口
        |--ThreadPoolExecutor 線程池的實現(xiàn)類
        |--ScheduledExecutorService 子接口:負責線程的調(diào)度
              |--ScheduledThreadPoolExecutor:繼承 ThreadPoolExecutor释树,
                                                實現(xiàn) ScheduledExecutorService

工具類:Executors

ExecutorService newFixedThreadPool():創(chuàng)建固定大小的線程池
ExecutorService newCachedThreadPool():緩存線程池肠槽,線程池的數(shù)量不固定,可以根據(jù)需求自動的更改數(shù)量奢啥。
ExecutorService newSingleThreadExecutor():創(chuàng)建單個線程池秸仙。線程池中只有一個線程
ScheduledExecutorService newScheduledThreadPool():創(chuàng)建固定大小的線程,可以狂遲或定時的執(zhí)行任務桩盲。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寂纪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捞蛋,老刑警劉巖孝冒,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拟杉,居然都是意外死亡迈倍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門捣域,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啼染,“玉大人,你說我怎么就攤上這事焕梅〖6欤” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵贞言,是天一觀的道長斜棚。 經(jīng)常有香客問我,道長该窗,這世上最難降的妖魔是什么弟蚀? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮酗失,結(jié)果婚禮上义钉,老公的妹妹穿的比我還像新娘。我一直安慰自己规肴,他們只是感情好捶闸,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拖刃,像睡著了一般删壮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兑牡,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天央碟,我揣著相機與錄音,去河邊找鬼均函。 笑死亿虽,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的边酒。 我是一名探鬼主播经柴,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼狸窘,長吁一口氣:“原來是場噩夢啊……” “哼墩朦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起翻擒,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤氓涣,失蹤者是張志新(化名)和其女友劉穎牛哺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劳吠,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡引润,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了痒玩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淳附。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蠢古,靈堂內(nèi)的尸體忽然破棺而出奴曙,到底是詐尸還是另有隱情,我是刑警寧澤草讶,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布洽糟,位于F島的核電站,受9級特大地震影響堕战,放射性物質(zhì)發(fā)生泄漏坤溃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一嘱丢、第九天 我趴在偏房一處隱蔽的房頂上張望薪介。 院中可真熱鬧,春花似錦越驻、人聲如沸昭灵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烂完。三九已至,卻和暖如春诵棵,著一層夾襖步出監(jiān)牢的瞬間抠蚣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工履澳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘶窄,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓距贷,卻偏偏與公主長得像柄冲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子忠蝗,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

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