亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

理解Java多線程之并發(fā)編程

 更新時(shí)間:2023年02月02日 08:34:24   作者:Seattle小小瓜  
這篇文章主要介紹了理解Java多線程之并發(fā)編程的相關(guān)資料,需要的朋友可以參考下

今天深度學(xué)習(xí)一下《Java并發(fā)編程的藝術(shù)》的第1章并發(fā)編程的挑戰(zhàn),深入理解Java多線程,看看多線程中的坑。

注意,哈肯的程序員讀書筆記并不是抄書,而是將書中精華內(nèi)容和哈肯的開發(fā)經(jīng)驗(yàn)相結(jié)合,給你系統(tǒng)地講述我對(duì)相關(guān)知識(shí)理解。如果你發(fā)現(xiàn)哈肯講的內(nèi)容跟書中內(nèi)容差異很大也不足為奇。

1 多線程的使用場(chǎng)景

在實(shí)際的商業(yè)系統(tǒng)中,為了提升程序的性能,我們經(jīng)常會(huì)使用到多線程。java多線程也是后臺(tái)開發(fā)崗、Android開發(fā)崗招聘面試和筆試時(shí)的熱門問題。至少,到目前為止我面試過的200多位開發(fā)同學(xué)時(shí),以及我曾經(jīng)的幾次求職被面試時(shí),多半都會(huì)問java多線程并發(fā)相關(guān)的問題。

2 多線程的缺點(diǎn)

多線程能充分利用多核CPU的特性,但并不意味著多線程就一定比單線程更快,并發(fā)編程也存在許多限制和挑戰(zhàn),例如多線程間的上下文切換會(huì)有開銷、多線程中的數(shù)據(jù)一致性問題、線程死鎖問題、系統(tǒng)資源限制。

2.1 上下文切換的開銷

(1)上下文切換的開銷

CPU使用時(shí)間片算法,將處理時(shí)間輪著分配給不同的線程,所以即使是單核CPU也支持多線程,這個(gè)時(shí)間片非常短,一般是幾十毫秒(ms),CPU不停的切換線程執(zhí)行,讓我們感覺多個(gè)線程是同時(shí)執(zhí)行的。CPU在切換線程前會(huì)保存上一個(gè)任務(wù)的狀態(tài),以便下次切換回這個(gè)線程時(shí),可以正常繼續(xù)執(zhí)行。我們把線程的狀態(tài)保存到再加載的過程稱為一次上下文切換。

《Java并發(fā)編程的藝術(shù)》作者寫了一段程序,讓2個(gè)線程同時(shí)不斷地做變量自增操作,結(jié)果證明用2個(gè)線程并行甚至比單線程串行更慢一些。通過使用Lmbench3測(cè)量上下文切換的時(shí)長(zhǎng),發(fā)現(xiàn)上述多線程代碼中每秒高達(dá)1000多次的線程上下文切換。因此不是多線程就一定更快,還要看在線程中干了什么。

(2)如何減少上下文切換

減少上下文切換的方法有:無鎖并發(fā)編程、CAS算法、減少不必要的線程、使用協(xié)程。

  • 無鎖并發(fā)編程。多線程競(jìng)爭(zhēng)鎖時(shí),會(huì)引起上下文切換,通過避免使用鎖,可以減少線程的切換,例如將數(shù)據(jù)用ID分段,不同的線程處理不同段的數(shù)據(jù)。
  • CAS算法。Java的Atomic包使用CAS算法來更新數(shù)據(jù),而不需要加鎖。
  • 減少不必要的線程。當(dāng)任務(wù)很少時(shí),盡量減少不必要的線程,避免造成大量線程都處于等待狀態(tài)。
  • 協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個(gè)任務(wù)間的切換。

2.2 多線程中的數(shù)據(jù)一致性問題

(1)線程中訪問外部數(shù)據(jù)的過程

每個(gè)線程都有自己的棧,保存線程中創(chuàng)建的局部變量,如果線程中使用到外部的變量,則線程通常會(huì)把改外部變量復(fù)制一份到線程棧中,當(dāng)修改完后,再將數(shù)據(jù)同步回外部。

(2)線程內(nèi)操作的原子性問題

一個(gè)操作會(huì)由多個(gè)cpu指令構(gòu)造。例如,創(chuàng)建對(duì)象操作大致分為幾步:為對(duì)象分配內(nèi)存、成員變量的值初始化、調(diào)用構(gòu)造方法、返回對(duì)象的引用;又例如,線程中對(duì)一個(gè)外部變量的賦值(修改)操作大致分為幾步:在當(dāng)前線程棧創(chuàng)建變量副本,修改變量值,將變量值的修改同步回外部,由于CPU的時(shí)間片機(jī)制,每個(gè)線程獲得時(shí)間片后,能執(zhí)行的指令數(shù)量是有限的,可能一個(gè)操作還未完成,而時(shí)間片到了,需要保存上下文并切換到下一個(gè)線程。因此,無法保證操作的原子性。

(3)共享數(shù)據(jù)的可見性問題

觀察上述的原子性問題中的例子,線程中對(duì)一個(gè)外部變量的賦值,可能線程A中剛創(chuàng)建了外部變量的副本,而線程B已經(jīng)對(duì)該外部變量進(jìn)行了修改,但線程A中是不知道的。即,一個(gè)線程對(duì)共享數(shù)據(jù)的修改,不能立刻被其他線程所看見,這就引起了數(shù)據(jù)一致性的問題。

(4)有序性問題

CPU單個(gè)核中,包含多個(gè)ALU單元(Arithmetic Logic Unit,即算術(shù)邏輯單元),用來執(zhí)行算數(shù)運(yùn)算和邏輯運(yùn)算。目前Intel的Haswell架構(gòu)的CPU(第四代酷睿處理器開始)有4個(gè)ALU單元。所以單核CPU在同一時(shí)刻是同時(shí)執(zhí)行多個(gè)指令的。CPU會(huì)把多條指令進(jìn)行重排序,把沒有依賴關(guān)系的指令同時(shí)放到各ALU中執(zhí)行。例如3個(gè)操作按如下順序?qū)懘a a=1; b=2; c=a+b; 由于a=1和b=2不存在依賴關(guān)系,2個(gè)指令可能會(huì)被重排序,而c=a+b存在依賴關(guān)系,這個(gè)指令一定會(huì)在前2個(gè)指令執(zhí)行完后再執(zhí)行,最終一定是c=3。請(qǐng)看如下代碼:

public class VolatileTest {
    private int a = 1;
    private boolean status = false;

    public void setStatus() {
        a = 2;
        status = true;
    }
 
    public void test() {
        if (status) {
            int b = a + 1;
            System.out.print(b);
        }
    }
}

(5)如何解決多線程的數(shù)據(jù)一致性問題

由于以上4點(diǎn),引發(fā)了多線程中數(shù)據(jù)的一致性問題。解決辦法主要有2個(gè):

  • 使用volatile關(guān)鍵字。相對(duì)synchronized來說,volatile是一種輕量級(jí)的同步機(jī)制,有2個(gè)作用:一是保證共享變量對(duì)所有線程的可見性,即本地副本修改后會(huì)強(qiáng)制刷新回外部;二是禁止指令重排序優(yōu)化。但要注意volatile只對(duì)單個(gè)變量讀/寫操作具有原子性,對(duì)i++這種復(fù)合操作(取值、加1、賦值)是無法保證其原子性的。要保證多線程中i++這種復(fù)合操作的原子性,可以改用CAS的實(shí)現(xiàn)類,例如用AtomicInteger代替int。
  • 加鎖。通過鎖來實(shí)現(xiàn)同一時(shí)間只有1個(gè)線程可以訪問共享變量。

2.3 線程死鎖問題

死鎖產(chǎn)生需要同時(shí)滿足4個(gè)條件:

  • 資源的互斥使用,即當(dāng)資源被一個(gè)線程占有時(shí),別的線程不能用。
  • 不可搶占,即資源請(qǐng)的求者不能從使用者手上強(qiáng)制奪取資源,只能等待對(duì)方釋放。
  • 請(qǐng)求和保持,即在請(qǐng)求其他資源的同時(shí)還保持對(duì)現(xiàn)有資源的占有。
  • 循環(huán)等待,例如線程1持有資源A,請(qǐng)求資源B,而線程2持有資源B,請(qǐng)求資源A。

下面的代碼演示對(duì)資源的請(qǐng)求和保持,持有res1不釋放,同時(shí)對(duì)res2進(jìn)行請(qǐng)求:

synchronized (res1) {
   ... // 執(zhí)行一些操作 
   synchronized (res2) {
      ...
   }
}

解決死鎖的方法

只要讓產(chǎn)生死鎖的4個(gè)條件中任何一個(gè)不成立,就可以避免死鎖。具體做法通常有:

  • 使用Lock接口的tryLock()。
  • 避免一個(gè)線程中同時(shí)獲得多個(gè)鎖。例如上面代碼所示,同時(shí)獲得res1和res2的鎖才能完成執(zhí)行。
  • 避免一個(gè)鎖中占用多個(gè)資源,即要縮小鎖的范圍。

Lock接口包含4個(gè)給對(duì)象加鎖的方法,如下所示:

public interface Lock {
     /**阻塞,直到另外一個(gè)線程釋放鎖*/
    void lock();
    
    /**以可被中斷的方式獲取鎖。調(diào)用正在等待獲得鎖的線程的interrupt()方法可中斷*/
    void lockInterruptibly() throws InterruptedException;
    
    /**非阻塞,嘗試的獲取鎖,如果獲取到則返回true,否則返回false*/
    boolean tryLock();
    /**阻塞,試圖獲取鎖,最多阻塞等待指定時(shí)間,如果獲取到則返回true,否則返回false*/
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    void unlock();
    ...
}
Lock lock = new ReentrantLock();
if(lock.tryLock()) {
    //成功獲得鎖
    try{
        ...
    } catch(Exception ex){
     
    } finally{
         lock.unlock();   //釋放鎖
    } 
} else {
    //不能獲取鎖
    ...
}
class Account {
    private int money;
    synchronized boolean transfer(Account target, int money){
        if (this.money > money) {
            this.money -= money;
            target.money += money;
            return true;
        }
        return false;
    } 
}

深入理解Java多線程(1) - Java并發(fā)編程的藝術(shù)_并發(fā)編程

class Account {
    private int money;
    boolean transfer(Account target, int money){
        synchronized(Account.class) {
            if (this.money > money) {
              this.money -= money;
              target.money += money;
              return true;
            }
        }
        return false;
    } 
}

2.4 系統(tǒng)資源限制

系統(tǒng)的資源總是有限的,無節(jié)制地創(chuàng)建大量的線程,只會(huì)造成大量的線程上下文切換的開銷,并不能實(shí)際提高程序的效率。按照大家的常規(guī)經(jīng)驗(yàn),如果是IO密集型,則線程池的核心線程數(shù)宜為2N+1;如果是
CPU密集型,則線程池核心線程數(shù)宜為N+1;其中N為CPU核數(shù)。

到此這篇關(guān)于理解Java多線程之并發(fā)編程的文章就介紹到這了,更多相關(guān)Java并發(fā)編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論