多線程和并行設(shè)計(jì)

線程的基礎(chǔ)

任務(wù)的概念:是實(shí)現(xiàn)Runnable接口的實(shí)例秕铛,也稱為可運(yùn)行對象,任務(wù)必須在線程中執(zhí)行
線程的概念:線程是一個(gè)任務(wù)從頭至尾的執(zhí)行流程的控制機(jī)制【注:現(xiàn)成不等同于任務(wù)】,在java中是Thread類創(chuàng)建的一個(gè)對象
兩者的關(guān)系:線程本質(zhì)上也是一個(gè)任務(wù)瞎颗,Thread類自身也實(shí)現(xiàn)了Runnable接口典徊,所以另外一種思路是:定義一個(gè)Thread的拓展類寄啼,然后實(shí)現(xiàn)run( )方法,再從客戶端創(chuàng)建這個(gè)類的對象钠乏,并調(diào)用start( )方法來啟動線程栖秕。但是這不是一個(gè)好方法,把任務(wù)和任務(wù)機(jī)制(線程晓避,是為了控制流程)混合在一起了簇捍。將任務(wù)混在了控制機(jī)制里面。
Java語言對多線程的支持:有一些列的接口和類俏拱,提供了多線程的創(chuàng)建和運(yùn)行暑塑,鎖定資源和避免沖突
從計(jì)算機(jī)來看:一個(gè)cpu一次運(yùn)行一個(gè)線程,多線程就可以讓程序的反應(yīng)更快锅必,執(zhí)行效率更高

Runnable接口:

只有一個(gè)run( )方法事格,JVM運(yùn)行任務(wù)(可運(yùn)行對象)時(shí)會自動調(diào)用這個(gè)方法。

任務(wù)和線程的應(yīng)用:

1.創(chuàng)建一個(gè)任務(wù)類:
需要有一個(gè)定義好的任務(wù)類(實(shí)現(xiàn)Runnable和覆寫override)况毅,然后用這個(gè)任務(wù)類的構(gòu)造方法創(chuàng)建一個(gè)任務(wù)
2.創(chuàng)建一個(gè)線程:
直接調(diào)用Java提供的Thread類創(chuàng)建

Thread thread = new Thread(Task)

3.開始線程:
調(diào)用線程對象的start( )方法分蓖,告訴JVM準(zhǔn)備開始運(yùn)行,然后JVM會自動加載任務(wù)類的run方法

thread.start( )

簡單的demo

package com.design.TaskAndThread;

public class TaskThreadDemo {
    public static void main(String[] args) {
        // 創(chuàng)建任務(wù)
        Runnable printA = new PrintChar('a', 100);
        Runnable printB = new PrintChar('b', 100);
        Runnable print100 = new PrintNum(100);
        // 創(chuàng)建線程
        Thread thread1 = new Thread(printA);
        Thread thread2 = new Thread(printB);
        Thread thread3 = new Thread(print100);
        //
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

// 定義一個(gè)任務(wù)類
class PrintChar implements Runnable {
    private char charToPrint;
    private int times;

    // 構(gòu)建方法不只是為了構(gòu)建一個(gè)對象時(shí)候才使用,也可以初始化屬性(私有屬性)
    public PrintChar(char a, int t) {
        this.charToPrint = a;
        this.times = t;
    }

    // 覆寫run方法尔许,讓JVM調(diào)用時(shí)知道怎么做
    @Override
    public void run() {
        for (int i = 0; i < times; i++) {
            System.out.println(charToPrint);
        }
    }

}

class PrintNum implements Runnable {
    private int lastNum;

    public PrintNum(int n) {
        this.lastNum = n;
    }

    @Override
    public void run() {
        for (int i = 0; i <= lastNum; i++) {
            System.out.println(i + " ");
        }
    }

}

demo里有三個(gè)程序,為了能夠同時(shí)運(yùn)行终娃,創(chuàng)建了三個(gè)線程
注:如果在

thread1.start();
thread2.start();
thread3.start();

改變?yōu)橹苯诱{(diào)用run( )

thread1.run();
thread2.run();
thread3.run();

就只是在單獨(dú)一個(gè)線程中執(zhí)行該方法味廊,沒有新的線程啟動,結(jié)果就是按順序執(zhí)行完thread1然后thread2然后thread3棠耕。

Thread類:

包含創(chuàng)建線程的構(gòu)造方法以及控制線程的很多方法余佛,線程的開始和暫定以及狀態(tài)的判斷和結(jié)束
在java中不建議用stop方法來停止線程,通過對Thread變量賦值為null來表明停止
常用的方法:

線程的兩種暫停:

1.Thread.yield( ) 執(zhí)行一次就臨時(shí)暫停窍荧,讓出cpu時(shí)間
在demo中的修改辉巡,在任務(wù)類中的run( )方法中添加

// 覆寫run方法,讓JVM調(diào)用時(shí)知道怎么做
    @Override
    public void run() {
        for (int i = 0; i < times; i++) {
            System.out.println(charToPrint);
                       // 添加暫停線程的方法蕊退,但不是運(yùn)行一次就暫停一次
            Thread.yield();
        }
    }

2.Thread.sleep(num) 將線程設(shè)置為休眠確保其他的線程啟動郊楣,休眠時(shí)間為毫秒數(shù)憔恳, 會拋出一個(gè)必檢異常IntettuptedException。如果一個(gè)循環(huán)中調(diào)用了sleep方法净蚤,就應(yīng)該將循環(huán)放在try-catch中钥组。這樣一出現(xiàn)錯(cuò)誤才會停止線程
體會一下這兩者的區(qū)別
首先是正確的方式:

try {
    while(....)
      Thread.sleep(1000);
     }
}
catch ( IntettuptedException ex) {
}

接下是錯(cuò)誤的方式:

while(....){
   try{
      Thread.sleep(1000);
     }
}
catch ( IntettuptedException ex) {
}

后面這種錯(cuò)誤的方式會導(dǎo)致,即使線程被中斷今瀑,它還繼續(xù)執(zhí)行程梦,特別注意,IDE自動添加try-catch時(shí)橘荠,會造成第二種屿附,一定要記得循環(huán)控制代碼在try-catch里
demo中修改,一樣在任務(wù)類中修改run( )方法


    @Override
    public void run() {
        for (int i = 0; i <= lastNum; i++) {
            System.out.println(i + " ");
            try {
                if (i >= 29) {
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

使一個(gè)線程等待另一個(gè)線程結(jié)束

anotherThread.join:在一個(gè)線程的run方法運(yùn)行中加入另外一個(gè)thread.join()哥童,然后原本運(yùn)行的線程會等待這個(gè)后加入的anotherThread執(zhí)行完(可以指定執(zhí)行的時(shí)間)

@Override
    public void run() {
        // 這是直接聲明一個(gè)線程并在里面裝載了任務(wù)
        Thread thread4 = new Thread(new PrintChar('Z', 100));
        thread4.start();
        for (int i = 0; i <= lastNum; i++) {
            System.out.println(i + " ");
            try {
                // 這是通過休眠暫停一個(gè)線程挺份,使用thread.join要注視掉,不然暫停的時(shí)候自動就運(yùn)行不用join了
//              if (i >= 29) {
//                  Thread.sleep(10);
//              }
                if (i == 50) {
                    thread4.join( );
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

線程的優(yōu)先級

默認(rèn)情況下如蚜,線程會繼承生成它的線程的優(yōu)先級(java中優(yōu)先級范圍為1~10)压恒,使用setPriority方法更改優(yōu)先級,默認(rèn)三個(gè)優(yōu)先級為(1 小错邦,5 正常探赫, 10 大)JVM會選擇優(yōu)先級大的可運(yùn)行線程,就是數(shù)字越大越早運(yùn)行撬呢,如果線程優(yōu)先級相同伦吠,則會用循環(huán)隊(duì)列給它們分配相同的cpu份額,稱為循環(huán)調(diào)度
注意:在編寫程序的時(shí)候魂拦,最好使用常量來賦值毛仪,數(shù)字可能后期會更改,
因?yàn)榈蛢?yōu)先級的線程必須等待高優(yōu)先級的線程運(yùn)行完芯勘,所以為了避免高優(yōu)先級的線程出現(xiàn)問題一直卡著導(dǎo)致低優(yōu)先級沒機(jī)會運(yùn)行箱靴,所以高優(yōu)先級會加入sleep或者yield方法來暫停。

線程池

線程池的作用:每個(gè)任務(wù)都會開始一個(gè)新的線程荷愕,這會限制吞吐并且造成性能降低衡怀。線程池來管理并發(fā)執(zhí)行任務(wù)。簡單點(diǎn)就是限制線程的數(shù)量
重要的接口和類:Executor接口來執(zhí)行線程池中的任務(wù)安疗,ExecutorService接口繼承Executor接口抛杨,然后管理和控制任務(wù),ExecutorService接口繼承Executor接口荐类,同樣是把管理和創(chuàng)建的功能解耦的設(shè)計(jì)(復(fù)習(xí)前面的任務(wù)和線程的設(shè)計(jì))怖现。Executor類實(shí)現(xiàn)了Exexutor接口和ExecutorService的接口,實(shí)現(xiàn)了接口的execute方法玉罐,并且提供了創(chuàng)建Executor對象的靜態(tài)方法
Executor類的兩個(gè)重要方法:1.newFixedThreadPool(num)這是創(chuàng)建一個(gè)限制最大線程的線程池2.newCachedThreadPool()這是在當(dāng)之前創(chuàng)建的線程可用時(shí)就不會創(chuàng)建新的屈嗤,如果沒有再創(chuàng)造新的線程潘拨,會為每一個(gè)任務(wù)都創(chuàng)建一個(gè)新的線程,所有的任務(wù)都會并發(fā)的進(jìn)行

線程池的工作原理:如果線程池中的完成了一個(gè)任務(wù)恢共,就能夠重新執(zhí)行另外一個(gè)任務(wù)战秋。如果線程池中的所有線程r都不是處于空閑狀態(tài),在(executor.shutdown)關(guān)閉前出現(xiàn)一個(gè)錯(cuò)誤關(guān)閉了一個(gè)線程讨韭,這時(shí)來了一個(gè)新的任務(wù)等待執(zhí)行脂信,就會創(chuàng)建一個(gè)新線程來代替。線程池中的線程在60s內(nèi)都沒有被使用就該終止它透硝。
demo

        // ====創(chuàng)建一個(gè)最大線程數(shù)的executor對象====
        // Executor定義了一個(gè)限制最大線程的線程池狰闪,然后用執(zhí)行器  管理接口聲明
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // ====提交任務(wù)到執(zhí)行器====
        // 線程執(zhí)行器給線程裝載任務(wù)
        executor.execute(new PrintChar('E', 100));
        executor.execute(new PrintChar('T', 50));
        executor.execute(new PrintNum(100));
        // ====關(guān)閉線程池(執(zhí)行器)====
        executor.shutdown();

線程同步

作用:協(xié)調(diào)相互依賴的線程的執(zhí)行,如果同一個(gè)資源被多個(gè)線程同時(shí)訪問濒生,可能會出現(xiàn)破壞埋泵,數(shù)據(jù)與真實(shí)的不符合

step b task1 task2
1 0 n = b + 1
2 0 n = b + 1
3 1 b = n
4 1 b =n

原因:是因?yàn)槿蝿?wù)2覆蓋了任務(wù)1的結(jié)果,因?yàn)閮蓚€(gè)同時(shí)訪問了一個(gè)公共資源罪治,稱為競爭狀態(tài)丽声,如果一個(gè)類的對象在多線程程序中沒有導(dǎo)致競爭狀態(tài),那才是線程安全的
demo這個(gè)demo里面運(yùn)用了很好的封裝觉义,注意理解雁社,然戶在判斷線程池任務(wù)時(shí)的while要好好琢磨一下

package com.design.TaskAndThread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AccountWithoutSynchronized {
    private static Account account = new Account();
    public static void main(String[] args) {
        // ====在線程里運(yùn)行,這里的線程池
        ExecutorService executor = Executors.newCachedThreadPool();
        // 這里通過循環(huán)創(chuàng)建了100個(gè)線程
        for(int i = 0; i < 100; i ++) {
            executor.execute(new AddPenyTask());
        }
        executor.shutdown();
        // isTerminated 如果線程池中所有任務(wù)都終止晒骇,返回true
        while (!executor.isTerminated()) {
        }
        System.out.println("賬戶是多少錢霉撵? " + account.getBalance());
    }
    // ====任務(wù)類,增加賬戶金錢洪囤,將具體的操作分剝開徒坡,純粹調(diào)用方法
    private static class AddPenyTask implements Runnable{
        @Override
        public void run() {
            account.deposit(1);
        }
    }
    // ====賬戶類,跟賬戶有關(guān)系的操作方法都在這里瘤缩,提供方法讓外面的調(diào)用====
    private static class Account {
        private int balance = 0;
        private int getBalance() {
            return balance;
        }
        // ====每次增加1====
        public void deposit(int amount) {
            int newBalance = amount + balance;
            try {
                // 增加效果喇完,線程休眠一會
                Thread.sleep(5);
            } catch (InterruptedException e) {
            }
            balance = newBalance;
        }
        
    }
}

線程安全的方法:

達(dá)到線程安全就必須讓線程沒有競爭公共資源,就得把程序中的這部分限制起來剥啤,這部分叫做臨界區(qū)何暮。
方法一: 將臨界區(qū)的方法加上限定關(guān)鍵字 synchronized,可以加在方法或是在語句上
方法二: 在執(zhí)行之前加上一把鎖铐殃,對于靜態(tài)方法要在類上加鎖,實(shí)例方法要給對象加鎖跨新。如果一個(gè)線程調(diào)用了一個(gè)對象的同步方法富腊,首先要給對象(類)加鎖,然后執(zhí)行這個(gè)方法域帐。最后解鎖赘被。在解鎖之前是整,另外一個(gè)調(diào)用那個(gè)對象(類)的線程會被堵塞,知道解鎖

synchronized關(guān)鍵字

給整個(gè)方法加上synchronized

public synchronized void deposit(int amount) 

給語句加上synchronized同步語句

優(yōu)點(diǎn)就是允許設(shè)置同步方法中的部分代碼民假,而不必是整個(gè)方法浮入,這大大增強(qiáng)了程序的并發(fā)能力

    private static class AddPenyTask implements Runnable{
        @Override
        public void run() {
            /* 這是不可行的方法,這里的this的參數(shù)必須是一個(gè)對象的引用
             * synchronized (this) {
                account.deposit(1);
            }*/
            synchronized (account) {
                account.deposit(1);
            }
        }
    }

這是在調(diào)用的時(shí)候

利用加鎖同步

java中可以采用鎖和狀態(tài)來同步線程羊异,
其實(shí)synchronized在執(zhí)行前都是隱式的加上一把鎖事秀,也有顯示的鎖可以使用。
Lock接口:一個(gè)鎖是Lock接口的實(shí)例野舶,定義了加鎖和釋放鎖的方法易迹。鎖也有newCondition()方法來創(chuàng)建任意個(gè)數(shù)的Condition對象,來進(jìn)行線程間的通信平道。
ReentrantLock類:是Lock的一個(gè)實(shí)現(xiàn)睹欲,創(chuàng)建兩種相互排斥的公平策略的鎖。策略為真時(shí)一屋,等待時(shí)間最長的線程首先得到鎖窘疮,公平策略為假的時(shí)候,鎖將隨機(jī)給線程冀墨。一個(gè)公平鎖的程序被多個(gè)線程訪問時(shí)闸衫,整體性能可能比默認(rèn)設(shè)置的差,但是在獲取鎖并且資源缺乏時(shí)可以更小的時(shí)間變化轧苫。
demo:

    public static class Account {
        // 創(chuàng)建一個(gè)鎖
        private static Lock lock = new ReentrantLock();
        private int balance = 0;

        public int getBalance() {
            return balance;
        }

        public void deposit(int amount) {
            // ====獲取lock后加上try-catch最后finally釋放鎖====
            // 獲取鎖
            lock.lock();
            try {
                int newBalance = balance + amount;
                Thread.sleep(5);
                balance = newBalance;
            } catch (InterruptedException e) {
            } finally {
                // 釋放鎖
                lock.unlock();
            }
        }
    }

在任務(wù)的執(zhí)行方法中的公共資源前加鎖楚堤,使用synchronized限定方法和語句的使用比顯示鎖簡單,但是顯示鎖對同步劇透狀態(tài)的線程更加直觀和靈活含懊。

線程間的寫作

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末身冬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子岔乔,更是在濱河造成了極大的恐慌酥筝,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雏门,死亡現(xiàn)場離奇詭異嘿歌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茁影,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門宙帝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人募闲,你說我怎么就攤上這事步脓。” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵靴患,是天一觀的道長仍侥。 經(jīng)常有香客問我,道長鸳君,這世上最難降的妖魔是什么农渊? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮或颊,結(jié)果婚禮上砸紊,老公的妹妹穿的比我還像新娘。我一直安慰自己饭宾,他們只是感情好批糟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著看铆,像睡著了一般徽鼎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弹惦,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天否淤,我揣著相機(jī)與錄音,去河邊找鬼棠隐。 笑死石抡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的助泽。 我是一名探鬼主播啰扛,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗡贺!你這毒婦竟也來了隐解?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤诫睬,失蹤者是張志新(化名)和其女友劉穎煞茫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摄凡,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡续徽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亲澡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钦扭。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖床绪,靈堂內(nèi)的尸體忽然破棺而出土全,到底是詐尸還是另有隱情捎琐,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布裹匙,位于F島的核電站,受9級特大地震影響末秃,放射性物質(zhì)發(fā)生泄漏概页。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一练慕、第九天 我趴在偏房一處隱蔽的房頂上張望惰匙。 院中可真熱鬧,春花似錦铃将、人聲如沸项鬼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绘盟。三九已至,卻和暖如春悯仙,著一層夾襖步出監(jiān)牢的瞬間龄毡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工锡垄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沦零,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓货岭,卻偏偏與公主長得像路操,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子千贯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

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