国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入淺出Java的多線(xiàn)程編程——第二篇

這篇具有很好參考價(jià)值的文章主要介紹了深入淺出Java的多線(xiàn)程編程——第二篇。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

目錄

前情回顧

1. 中斷一個(gè)線(xiàn)程

1.1 中斷的API

1.2 小結(jié)

2. 等待一個(gè)線(xiàn)程

?2.1 等待的API

3. 線(xiàn)程的狀態(tài)

3.1 貫徹線(xiàn)程的所有狀態(tài)

3.2 線(xiàn)程狀態(tài)和狀態(tài)轉(zhuǎn)移的意義

4.?多線(xiàn)程帶來(lái)的的風(fēng)險(xiǎn)-線(xiàn)程安全 (重點(diǎn))

4.1 觀察線(xiàn)程不安全

4.2 線(xiàn)程安全的概念

4.3 線(xiàn)程不安全的原因

4.3.1 修改共享數(shù)據(jù)

4.3.2 原子性

4.3.3 可見(jiàn)性

4.3.4 代碼順序性

4.4 解決之前的線(xiàn)程不安全問(wèn)題


前情回顧

操作系統(tǒng)、進(jìn)程和線(xiàn)程_木子斤欠木同的博客-CSDN博客

深入淺出Java的多線(xiàn)程編程——第一篇_木子斤欠木同的博客-CSDN博客

讓我們來(lái)回顧一下,第一篇多線(xiàn)程的內(nèi)容:

1. 多線(xiàn)程:

(1)線(xiàn)程的概念

(2)進(jìn)程和線(xiàn)程的區(qū)別

(3)Java代碼如何創(chuàng)建線(xiàn)程

①繼承Thread重寫(xiě)run

②實(shí)現(xiàn)Runnable接口重寫(xiě)run,將該實(shí)現(xiàn)類(lèi)作為參數(shù)傳給Thread的構(gòu)造方法

③繼承Thread,匿名內(nèi)部類(lèi)

④實(shí)現(xiàn)Runnable,匿名內(nèi)部類(lèi)

⑤lambda表達(dá)式

2. Thread的常用屬性

start方法,真正從系統(tǒng)這里,創(chuàng)建一個(gè)線(xiàn)程,新的線(xiàn)程將會(huì)執(zhí)行run方法。

run方法:表示線(xiàn)程的入口方法是啥(線(xiàn)程啟動(dòng)起來(lái),要執(zhí)行哪些邏輯)(run方法不是讓程序猿調(diào)用的,要交給系統(tǒng)去自動(dòng)調(diào)用)【換個(gè)角度理解:我們可以把線(xiàn)程的run方法理解為main方法,都是系統(tǒng)去自動(dòng)調(diào)用】

1. 中斷一個(gè)線(xiàn)程

李四一旦進(jìn)到工作狀態(tài),他就會(huì)按照行動(dòng)指南上的步驟去進(jìn)行工作,不完成是不會(huì)結(jié)束的。但有時(shí)我們需要增加一些機(jī)制,例如老板突然來(lái)電話(huà)了,說(shuō)轉(zhuǎn)賬的對(duì)方是個(gè)騙子,需要趕緊停止轉(zhuǎn)賬,那張三該如何通知李四停止呢?這就涉及到我們的停止線(xiàn)程的方式了。

本質(zhì)上來(lái)說(shuō),讓一個(gè)線(xiàn)程終止,辦法就一種,讓該線(xiàn)程的入口方法執(zhí)行完畢!也就是讓run跑完!

目前常見(jiàn)的有以下兩種方式:

  • 通過(guò)共享的標(biāo)記來(lái)進(jìn)行溝通
  • 調(diào)用 interrupt() 方法來(lái)通知【記住,只是通知而已】

示例1:使用自定義的變量來(lái)作為標(biāo)志位

  • 需要給標(biāo)志位上加 volatile 關(guān)鍵字(這個(gè)關(guān)鍵字的功能后面介紹).
    public static volatile boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!isQuit){
                System.out.println(Thread.currentThread().getName() + " : 別管我,我忙著轉(zhuǎn)正!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " : 啊!險(xiǎn)些誤了大事");
        });
        System.out.println(Thread.currentThread().getName() + " : 讓李四開(kāi)始轉(zhuǎn)賬");
        t.start();
        Thread.sleep(10*1000);
        System.out.println(Thread.currentThread().getName() + " : 老板來(lái)電話(huà)了,得趕緊通知李四對(duì)方是個(gè)騙子!");
        isQuit = true;
    }

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

這里的? ?public static volatile boolean isQuit = false; 為什么需要加static,因?yàn)閙ain函數(shù)被static修飾,所以在main內(nèi)部用到的成員變量要加static

示例2:使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定義標(biāo)志位。

1.1 中斷的API

  • Thread 內(nèi)部包含了一個(gè) boolean 類(lèi)型的變量作為線(xiàn)程是否被中斷的標(biāo)記。
方法 說(shuō)明
pubilc void interrupt() 中斷對(duì)象關(guān)聯(lián)的線(xiàn)程,如果線(xiàn)程正在阻塞,會(huì)把線(xiàn)程喚醒,則以異常的方式通知,然后吧標(biāo)志位設(shè)置為true
public static boolean interrupted() 判斷當(dāng)前線(xiàn)程的中斷標(biāo)志位是否設(shè)置,調(diào)用后清除標(biāo)志位
public boolean isInterrupted() 判斷對(duì)象關(guān)聯(lián)的線(xiàn)程的標(biāo)志位是否設(shè)置,調(diào)用后不清除標(biāo)志位
  • 使用 thread 對(duì)象的 interrupted() 方法通知線(xiàn)程結(jié)束.
public class Thread4 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("hello t");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

我們可以發(fā)現(xiàn),調(diào)用t.interrupt方法的時(shí)候,線(xiàn)程并沒(méi)有真的結(jié)束,而是打印了個(gè)異常信息,又繼續(xù)執(zhí)行了

1.2 小結(jié)

interrupt方法的作用:

(1)設(shè)置標(biāo)志位為true

(2)如果該線(xiàn)程正在阻塞中(比如執(zhí)行了sleep)此時(shí)就會(huì)把阻塞狀態(tài)喚醒,通過(guò)拋出異常的方式讓sleep立即結(jié)束

注意:一個(gè)非常重要的問(wèn)題,當(dāng)sleep被喚醒的時(shí)候(只有sleep被喚醒才會(huì)重置標(biāo)志位),sleep自動(dòng)地把isInterrupted標(biāo)志位給清空了(true - > false),這導(dǎo)致下次循環(huán),循環(huán)仍然可以繼續(xù)執(zhí)行了~~

有的開(kāi)關(guān),是按下之后,就按下去了

有的開(kāi)關(guān),是按下去之后,自動(dòng)彈起(sleep就屬于這種)

一種極端的情況:

如果設(shè)置interrupt的時(shí)候,恰好sleep剛醒,這個(gè)時(shí)候趕巧了,執(zhí)行到下一輪循環(huán)條件就直接結(jié)束了。但是這種概率非常低,畢竟sleep的時(shí)間已經(jīng)占據(jù)了整個(gè)循環(huán)體的99.999%的時(shí)間了

如果需要結(jié)束循環(huán),就得在catch中搞個(gè)break

public class Thread4 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("hello t");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
                   break;
               }
           }
        });
        t.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

小結(jié)一下:

如果sleep執(zhí)行的時(shí)候看到這個(gè)標(biāo)志位是false,sleep正常進(jìn)行休眠操作

如果當(dāng)前的標(biāo)志位為true

sleep無(wú)論是剛剛執(zhí)行還是已經(jīng)執(zhí)行了一般,都會(huì)觸發(fā)兩件事

(1)立即拋出異常

(2)清空標(biāo)志位為false?

再下次循環(huán),到sleep

由于當(dāng)前標(biāo)志位本身是false,就啥也不干~~

總結(jié)到這里,就有小伙伴有疑問(wèn)了,為什么sleep要清空標(biāo)志位呢?

目的就是為了讓線(xiàn)程自身能夠?qū)τ诰€(xiàn)程何時(shí)結(jié)束,有一個(gè)更明確的控制~~

當(dāng)前,interrupt方法,效果不是讓線(xiàn)程立即結(jié)束,而是告訴他,你該結(jié)束了,至于他是否真的要立即結(jié)束還是等會(huì)結(jié)束,都是由它本線(xiàn)程的代碼來(lái)控制~~,interrupt只是通知,而非“命令”!

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

有的朋友就好奇了,我如果不加sleep,這些能令線(xiàn)程阻塞的代碼,那是不是就能讓該線(xiàn)程直接結(jié)束呢?對(duì)的,是可以,但是工作中沒(méi)人會(huì)這么寫(xiě)代碼,一個(gè)不可控的線(xiàn)程是多么可怕!

這里就可以又引出一個(gè)問(wèn)題,java為啥不強(qiáng)制制定“命令結(jié)束”的操作呢?

只要調(diào)用interrupt就立即結(jié)束?

答:主要是設(shè)定成這種,對(duì)線(xiàn)程來(lái)說(shuō)非常不友好~,線(xiàn)程t何時(shí)結(jié)束,一定是t自己要最清楚,交給t自身來(lái)決定比較好!

2. 等待一個(gè)線(xiàn)程

有時(shí),我們需要等待一個(gè)線(xiàn)程完成它的工作后,才能進(jìn)行自己的下一步工作。例如,張三只有等李四轉(zhuǎn)賬成功,才決定是否存錢(qián),這時(shí)我們需要一個(gè)方法明確等待線(xiàn)程的結(jié)束。

public class Thread5 {
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
          for(int i = 0;i < 3;i++){
              try {
                  System.out.println(Thread.currentThread().getName() + " : 我正在工作!");
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
            System.out.println(Thread.currentThread().getName() + " : 我的工作結(jié)束了!");
        };

        Thread t1 = new Thread(run,"李四");
        Thread t2 = new Thread(run,"張三");
        System.out.println("李四先工作");
        t1.start();
        t1.join();
        System.out.println("李四做完了,張三開(kāi)始工作!");
        t2.start();
        t2.join();
        System.out.println("王五做完了,張三開(kāi)始工作!");
        System.out.println("兩人都工作結(jié)束了!");
    }
}

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

在main線(xiàn)程中調(diào)用t1.join()表示main線(xiàn)程要等t1線(xiàn)程跑完,main線(xiàn)程才可以繼續(xù)執(zhí)行。

(1)main線(xiàn)程調(diào)用t1.join()的時(shí)候,如果t1還在運(yùn)行,此時(shí)main線(xiàn)程阻塞,知道t執(zhí)行完畢(t1的run執(zhí)行完了),main線(xiàn)程才從阻塞中解除,才繼續(xù)執(zhí)行。

(2)main線(xiàn)程調(diào)用t.join()的時(shí)候,如果t已經(jīng)結(jié)束了,此時(shí)join就不會(huì)阻塞,會(huì)立即執(zhí)行下去。

如果把兩個(gè)join方法注釋掉,就會(huì)CPU的搶占式調(diào)用的典型例子:

public class Thread5 {
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
          for(int i = 0;i < 3;i++){
              try {
                  System.out.println(Thread.currentThread().getName() + " : 我正在工作!");
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
            System.out.println(Thread.currentThread().getName() + " : 我的工作結(jié)束了!");
        };

        Thread t1 = new Thread(run,"李四");
        Thread t2 = new Thread(run,"張三");
        System.out.println("李四先工作");
        t1.start();
//        t1.join();
        System.out.println("李四做完了,張三開(kāi)始工作!");
        t2.start();
//        t2.join();
        System.out.println("王五做完了,張三開(kāi)始工作!");
        System.out.println("兩人都工作結(jié)束了!");
    }
}

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

?2.1 等待的API

方法 說(shuō)明
public void join() 等待線(xiàn)程結(jié)束
public void join(long millis) 等待線(xiàn)程結(jié)束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度

3. 線(xiàn)程的狀態(tài)

3.1 貫徹線(xiàn)程的所有狀態(tài)

線(xiàn)程的狀態(tài)是一個(gè)枚舉類(lèi)型 Thread.State

public class Thread6 {
    public static void main(String[] args) {
        for (Thread.State state:Thread.State.values()) {
            System.out.println(state);
        }
    }
}

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

  • NEW: 安排了工作, 還未開(kāi)始行動(dòng)
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即將開(kāi)始工作.
  • BLOCKED: 這幾個(gè)都表示排隊(duì)等著其他事情
  • WAITING: 這幾個(gè)都表示排隊(duì)等著其他事情
  • TIMED_WAITING: 這幾個(gè)都表示排隊(duì)等著其他事情
  • TERMINATED: 工作完成了.

3.2 線(xiàn)程狀態(tài)和狀態(tài)轉(zhuǎn)移的意義

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

大家不要被這個(gè)狀態(tài)轉(zhuǎn)移圖嚇到,我們重點(diǎn)是要理解狀態(tài)的意義以及各個(gè)狀態(tài)的具體意思。

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

舉個(gè)栗子:
? ? ? ?剛把李四、王五找來(lái),還是給他們?cè)诎才湃蝿?wù),沒(méi)讓他們行動(dòng)起來(lái),就是 NEW 狀態(tài);
當(dāng)李四、王五開(kāi)始去窗口排隊(duì),等待服務(wù),就進(jìn)入到 RUNNABLE 狀態(tài)。該狀態(tài)并不表示已經(jīng)被銀行工。
? ? ? ?作人員開(kāi)始接待,排在隊(duì)伍中也是屬于該狀態(tài),即可被服務(wù)的狀態(tài),是否開(kāi)始服務(wù),則看調(diào)度器的調(diào)度;
? ? ? ?當(dāng)李四、王五因?yàn)橐恍┦虑樾枰ッΓ缧枰顚?xiě)信息、回家取證件、發(fā)呆一會(huì)等等時(shí),進(jìn)入
? ? ? ? BLOCKED 、 WATING 、 TIMED_WAITING 狀態(tài),至于這些狀態(tài)的細(xì)分,我們以后再詳解;
如果李四、王五已經(jīng)忙完,為 TERMINATED 狀態(tài)。所以,之前我們學(xué)過(guò)的 isAlive() 方法,可以認(rèn)為是處于不是 NEW 和 TERMINATED 的狀態(tài)都是活著的。

4.?多線(xiàn)程帶來(lái)的的風(fēng)險(xiǎn)-線(xiàn)程安全 (重點(diǎn))

本質(zhì)是因?yàn)榫€(xiàn)程之間的調(diào)度順序的不確定性

4.1 觀察線(xiàn)程不安全

public class Thread7 {
    static int count = 0;


    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for(int i = 0;i < 5000;i++){
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for(int i = 0;i < 5000;i++){
                count++;
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }

}

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

?

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

由于當(dāng)前這兩線(xiàn)程調(diào)度的順序是無(wú)序的~~

你也不知道這兩線(xiàn)程自增的過(guò)程中,到底經(jīng)歷了什么

有多少次是“順序執(zhí)行”,有多少次是“交錯(cuò)執(zhí)行”不知道!

得到的結(jié)果是啥也就是不確定的!

這里就引出了一個(gè)問(wèn)題,出現(xiàn)bug之后,得到的結(jié)果一定是 <= 10000,或者結(jié)果一定是 >= 5000?

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

CPU調(diào)用是以原語(yǔ)為單位的!

4.2 線(xiàn)程安全的概念

想給出一個(gè)線(xiàn)程安全的確切定義是復(fù)雜的,但我們可以這樣認(rèn)為:
如果多線(xiàn)程環(huán)境下代碼運(yùn)行的結(jié)果是符合我們預(yù)期的,即在單線(xiàn)程環(huán)境應(yīng)該的結(jié)果,則說(shuō)這個(gè)程序是線(xiàn)程安全的。

4.3 線(xiàn)程不安全的原因

4.3.1 修改共享數(shù)據(jù)

上面的線(xiàn)程不安全的代碼中, 涉及到多個(gè)線(xiàn)程針對(duì) counter.count 變量進(jìn)行修改.
此時(shí)這個(gè) count 是一個(gè)多個(gè)線(xiàn)程都能訪(fǎng)問(wèn)到的 "共享數(shù)據(jù)"!

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

count 這個(gè)變量就是在堆上. 因此可以被多個(gè)線(xiàn)程共享訪(fǎng)問(wèn).?

4.3.2 原子性

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

什么是原子性
我們把一段代碼想象成一個(gè)房間,每個(gè)線(xiàn)程就是要進(jìn)入這個(gè)房間的人。如果沒(méi)有任何機(jī)制保證,A進(jìn)入房間之后,還沒(méi)有出來(lái);B 是不是也可以進(jìn)入房間,打斷 A 在房間里的隱私。這個(gè)就是不具備原子性的。
那我們應(yīng)該如何解決這個(gè)問(wèn)題呢?是不是只要給房間加一把鎖,A 進(jìn)去就把門(mén)鎖上,其他人是不是就進(jìn)不來(lái)了。這樣就保證了這段代碼的原子性了。有時(shí)也把這個(gè)現(xiàn)象叫做同步互斥,表示操作是互相排斥的。?

一條 java 語(yǔ)句不一定是原子的,也不一定只是一條指令

比如剛才我們看到的 n++,其實(shí)是由三步操作組成的:

  1. 從內(nèi)存把數(shù)據(jù)讀到 CPU
  2. 進(jìn)行數(shù)據(jù)更新
  3. 把數(shù)據(jù)寫(xiě)回到 CPU

不保證原子性會(huì)給多線(xiàn)程帶來(lái)什么問(wèn)題
如果一個(gè)線(xiàn)程正在對(duì)一個(gè)變量操作,中途其他線(xiàn)程插入進(jìn)來(lái)了,如果這個(gè)操作被打斷了,結(jié)果就可能是錯(cuò)誤的。

這點(diǎn)也和線(xiàn)程的搶占式調(diào)度密切相關(guān). 如果線(xiàn)程不是 "搶占" 的, 就算沒(méi)有原子性, 也問(wèn)題不大。

4.3.3 可見(jiàn)性

可見(jiàn)性指一個(gè)線(xiàn)程對(duì)共享變量值的修改,能夠及時(shí)地被其他線(xiàn)程看到。

Java 內(nèi)存模型 (JMM): Java虛擬機(jī)規(guī)范中定義了Java內(nèi)存模型.
目的是屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪(fǎng)問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的并發(fā)效果.

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

  1. 線(xiàn)程之間的共享變量存在 主內(nèi)存 (Main Memory).
  2. 每一個(gè)線(xiàn)程都有自己的 "工作內(nèi)存" (Working Memory) .
  3. 當(dāng)線(xiàn)程要讀取一個(gè)共享變量的時(shí)候, 會(huì)先把變量從主內(nèi)存拷貝到工作內(nèi)存, 再?gòu)墓ぷ鲀?nèi)存讀取數(shù)據(jù).
  4. 當(dāng)線(xiàn)程要修改一個(gè)共享變量的時(shí)候, 也會(huì)先修改工作內(nèi)存中的副本, 再同步回主內(nèi)存.?

由于每個(gè)線(xiàn)程有自己的工作內(nèi)存, 這些工作內(nèi)存中的內(nèi)容相當(dāng)于同一個(gè)共享變量的 "副本". 此時(shí)修改線(xiàn)程1 的工作內(nèi)存中的值, 線(xiàn)程2 的工作內(nèi)存不一定會(huì)及時(shí)變化.

1) 初始情況下, 兩個(gè)線(xiàn)程的工作內(nèi)存內(nèi)容一致.

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

2) 一旦線(xiàn)程1 修改了 a 的值, 此時(shí)主內(nèi)存不一定能及時(shí)同步. 對(duì)應(yīng)的線(xiàn)程2 的工作內(nèi)存的 a 的值也不一定能及時(shí)同步.?

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

這個(gè)時(shí)候代碼中就容易出現(xiàn)問(wèn)題.

此時(shí)引入了兩個(gè)問(wèn)題:?

  • 為啥要整這么多內(nèi)存?
  • 為啥要這么麻煩的拷來(lái)拷去?

1) 為啥整這么多內(nèi)存?
實(shí)際并沒(méi)有這么多 "內(nèi)存". 這只是 Java 規(guī)范中的一個(gè)術(shù)語(yǔ), 是屬于 "抽象" 的叫法.
所謂的 "主內(nèi)存" 才是真正硬件角度的 "內(nèi)存". 而所謂的 "工作內(nèi)存", 則是指 CPU 的寄存器和高速緩存.

2) 為啥要這么麻煩的拷來(lái)拷去?
因?yàn)?CPU 訪(fǎng)問(wèn)自身寄存器的速度以及高速緩存的速度, 遠(yuǎn)遠(yuǎn)超過(guò)訪(fǎng)問(wèn)內(nèi)存的速度(快了 3 - 4 個(gè)數(shù)量級(jí), 也就是幾千倍, 上萬(wàn)倍)

比如某個(gè)代碼中要連續(xù) 10 次讀取某個(gè)變量的值, 如果 10 次都從內(nèi)存讀, 速度是很慢的. 但是如果只是第一次從內(nèi)存讀, 讀到的結(jié)果緩存到 CPU 的某個(gè)寄存器中, 那么后 9 次讀數(shù)據(jù)就不必直接訪(fǎng)問(wèn)內(nèi)存了,效率就大大提高了

那么接下來(lái)問(wèn)題又來(lái)了, 既然訪(fǎng)問(wèn)寄存器速度這么快, 還要內(nèi)存干啥??
答案就是一個(gè)字: 貴

深入淺出Java的多線(xiàn)程編程——第二篇,Java的多線(xiàn)程編程,java,開(kāi)發(fā)語(yǔ)言

值的一提的是, 快和慢都是相對(duì)的. CPU 訪(fǎng)問(wèn)寄存器速度遠(yuǎn)遠(yuǎn)快于內(nèi)存, 但是內(nèi)存的訪(fǎng)問(wèn)速度又遠(yuǎn)遠(yuǎn)快于硬盤(pán)。
對(duì)應(yīng)的, CPU 的價(jià)格最貴, 內(nèi)存次之, 硬盤(pán)最便宜。

4.3.4 代碼順序性

什么是代碼重排序
一段代碼是這樣的:

  1. 去前臺(tái)取下 U 盤(pán)
  2. 去教室寫(xiě) 10 分鐘作業(yè)
  3. 去前臺(tái)取下快遞

如果是在單線(xiàn)程情況下,JVM、CPU指令集會(huì)對(duì)其進(jìn)行優(yōu)化,比如,按 1->3->2的方式執(zhí)行,也是沒(méi)問(wèn)題,可以少跑一次前臺(tái),這種叫做指令重排序。

編譯器對(duì)于指令重排序的前提是 "保持邏輯不發(fā)生變化". 這一點(diǎn)在單線(xiàn)程環(huán)境下比較容易判斷, 但是在多線(xiàn)程環(huán)境下就沒(méi)那么容易了, 多線(xiàn)程的代碼執(zhí)行復(fù)雜程度更高, 編譯器很難在編譯階段對(duì)代碼的執(zhí)行效果進(jìn)行預(yù)測(cè), 因此激進(jìn)的重排序很容易導(dǎo)致優(yōu)化后的邏輯和之前不等價(jià).

重排序是一個(gè)比較復(fù)雜的話(huà)題, 涉及到 CPU 以及編譯器的一些底層工作原理, 此處不做過(guò)多討論文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-731038.html

4.4 解決之前的線(xiàn)程不安全問(wèn)題

public class Thread7 {
    static int count = 0;


    public static void main(String[] args) {
        Object o = new Object();
        Thread t1 = new Thread(() -> {

            synchronized (o) {
                for(int i = 0;i < 5000;i++){
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (o) {
                for(int i = 0;i < 5000;i++){
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }

}

到了這里,關(guān)于深入淺出Java的多線(xiàn)程編程——第二篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深入淺出Java多線(xiàn)程(十一):AQS

    深入淺出Java多線(xiàn)程(十一):AQS

    大家好,我是你們的老伙計(jì)秀才!今天帶來(lái)的是[深入淺出Java多線(xiàn)程]系列的第十一篇內(nèi)容:AQS( AbstractQueuedSynchronizer )。大家覺(jué)得有用請(qǐng)點(diǎn)贊,喜歡請(qǐng)關(guān)注!秀才在此謝過(guò)大家了!??! 在現(xiàn)代多核CPU環(huán)境中,多線(xiàn)程編程已成為提升系統(tǒng)性能和并發(fā)處理能力的關(guān)鍵手段。然而

    2024年03月12日
    瀏覽(30)
  • 深入淺出Java多線(xiàn)程(十):CAS

    深入淺出Java多線(xiàn)程(十):CAS

    大家好,我是你們的老伙計(jì)秀才!今天帶來(lái)的是[深入淺出Java多線(xiàn)程]系列的第十篇內(nèi)容:CAS。大家覺(jué)得有用請(qǐng)點(diǎn)贊,喜歡請(qǐng)關(guān)注!秀才在此謝過(guò)大家了?。?! 在多線(xiàn)程編程中,對(duì)共享資源的安全訪(fǎng)問(wèn)和同步控制是至關(guān)重要的。傳統(tǒng)的鎖機(jī)制,如synchronized和ReentrantLock等

    2024年03月11日
    瀏覽(26)
  • 【計(jì)算機(jī)視覺(jué)中的多視圖幾何系列】深入淺出理解針孔相機(jī)模型

    【計(jì)算機(jī)視覺(jué)中的多視圖幾何系列】深入淺出理解針孔相機(jī)模型

    溫故而知新,可以為師矣! 《計(jì)算機(jī)視覺(jué)中的多視圖幾何-第五章》-Richard Hartley, Andrew Zisserman. 1.1 投影中心/攝像機(jī)中心/光心 投影中心 稱(chēng)為 攝像機(jī)中心 ,也稱(chēng)為 光心 。投影中心位于一個(gè)歐式坐標(biāo)系的原點(diǎn)。 1.2 圖像平面/聚焦平面 平面 Z = f Z=f Z = f 被稱(chēng)為 圖像平面 或 聚焦

    2024年02月03日
    瀏覽(30)
  • 深入淺出線(xiàn)程池

    線(xiàn)程 (thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際 運(yùn)作單位。一條線(xiàn)程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線(xiàn)程,每條線(xiàn) 程并行執(zhí)行不同的任務(wù)。 既然我們創(chuàng)建了線(xiàn)程,那為何我們直接調(diào)用方法和我們調(diào)

    2024年02月08日
    瀏覽(24)
  • 深入淺出C++ ——線(xiàn)程庫(kù)

    深入淺出C++ ——線(xiàn)程庫(kù)

    ??在C++11之前,涉及到多線(xiàn)程問(wèn)題,都是和平臺(tái)相關(guān)的,比如windows和linux下各有自己的接口,這使得代碼的可移植性比較差。C++11中最重要的特性就是對(duì)線(xiàn)程進(jìn)行支持了,使得C++在并行編程時(shí)不需要依賴(lài)第三方庫(kù),而且在原子操作中還引入了 原子類(lèi) 的概念。要使用標(biāo)準(zhǔn)庫(kù)中

    2024年02月03日
    瀏覽(98)
  • “深入理解Java的多線(xiàn)程編程“

    多線(xiàn)程編程是指在一個(gè)程序中同時(shí)運(yùn)行多個(gè)線(xiàn)程,以提高程序的并發(fā)性和性能。Java是一門(mén)支持多線(xiàn)程編程的強(qiáng)大編程語(yǔ)言,提供了豐富的多線(xiàn)程相關(guān)類(lèi)和接口。 在Java中,可以通過(guò)以下方式實(shí)現(xiàn)多線(xiàn)程編程: 繼承Thread類(lèi):創(chuàng)建一個(gè)繼承自Thread類(lèi)的子類(lèi),并重寫(xiě)run()方法,在

    2024年02月13日
    瀏覽(53)
  • 【深入淺出C#】章節(jié) 8: 網(wǎng)絡(luò)編程和遠(yuǎn)程通信

    計(jì)算機(jī)網(wǎng)絡(luò)是指連接多臺(tái)計(jì)算機(jī)設(shè)備,通過(guò)通信鏈路共享資源和信息的系統(tǒng)。它構(gòu)建了一個(gè)相互連接的世界,使得人們可以在不同地點(diǎn)進(jìn)行數(shù)據(jù)交換和資源共享。網(wǎng)絡(luò)編程是指在計(jì)算機(jī)網(wǎng)絡(luò)中,使用編程語(yǔ)言進(jìn)行通信和數(shù)據(jù)傳輸?shù)募夹g(shù)?,F(xiàn)代應(yīng)用中,網(wǎng)絡(luò)編程發(fā)揮著重要作用

    2024年02月07日
    瀏覽(26)
  • 【深入淺出C#】章節(jié) 5: 高級(jí)面向?qū)ο缶幊蹋悍盒途幊毯图项?lèi)型

    高級(jí)面向?qū)ο缶幊淌窃诨A(chǔ)面向?qū)ο缶幊痰幕A(chǔ)上進(jìn)一步深入和拓展的一種編程范式。它強(qiáng)調(diào)封裝、繼承和多態(tài)的概念,并引入了泛型編程和集合類(lèi)型等高級(jí)特性。高級(jí)面向?qū)ο缶幊烫峁┝烁`活、可擴(kuò)展和可復(fù)用的代碼結(jié)構(gòu),能夠幫助開(kāi)發(fā)者構(gòu)建更復(fù)雜、更高效的應(yīng)用程序

    2024年02月16日
    瀏覽(42)
  • 【深入淺出C#】章節(jié) 4: 面向?qū)ο缶幊袒A(chǔ):封裝、繼承和多態(tài)

    封裝、繼承和多態(tài)是面向?qū)ο缶幊讨械暮诵母拍?,它們?duì)于構(gòu)建靈活、可擴(kuò)展和可維護(hù)的軟件系統(tǒng)至關(guān)重要。 封裝(Encapsulation)通過(guò)將數(shù)據(jù)和相關(guān)操作封裝在一個(gè)類(lèi)中,隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié),并提供公共接口來(lái)與外部進(jìn)行交互。封裝有助于保護(hù)數(shù)據(jù)的完整性和安全性,同時(shí)提

    2024年02月10日
    瀏覽(27)
  • 【深入淺出C#】章節(jié) 8: 網(wǎng)絡(luò)編程和遠(yuǎn)程通信:網(wǎng)絡(luò)編程和遠(yuǎn)程通信

    計(jì)算機(jī)網(wǎng)絡(luò)是指連接多臺(tái)計(jì)算機(jī)設(shè)備,通過(guò)通信鏈路共享資源和信息的系統(tǒng)。它構(gòu)建了一個(gè)相互連接的世界,使得人們可以在不同地點(diǎn)進(jìn)行數(shù)據(jù)交換和資源共享。網(wǎng)絡(luò)編程是指在計(jì)算機(jī)網(wǎng)絡(luò)中,使用編程語(yǔ)言進(jìn)行通信和數(shù)據(jù)傳輸?shù)募夹g(shù)。現(xiàn)代應(yīng)用中,網(wǎng)絡(luò)編程發(fā)揮著重要作用

    2024年02月12日
    瀏覽(22)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包