最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java多線程之搞定最后一公里詳解

 更新時(shí)間:2021年10月11日 14:04:24   作者:愛敲代碼的小高  
Java 給多線程編程提供了內(nèi)置的支持。 一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù),多線程是多任務(wù)的一種特別的形式,但多線程使用了更小的資源開銷

緒論

上期介紹了多線程的概念、優(yōu)勢、創(chuàng)建方法以及幾個(gè)常用的關(guān)鍵字。有了之前的基礎(chǔ)過后,我們來討論討論線程安全問題以及其他線程進(jìn)階知識。

一:線程安全問題

1.1 提出問題

首先,給大家看一下這個(gè)代碼:

public class yy1 {
    private static class Counter {
        private long n = 0;
        public void increment() {
            n++;
        }
        public void decrement() {
            n--;
        }
        public long value() {
            return n;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final int COUNT = 1000_0000;
        Counter counter = new Counter();
        Thread thread = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                counter.increment();
            }
        }, "李四");
        thread.start();
        for (int i = 0; i < COUNT; i++) {
            counter.decrement();
        }
        thread.join();
// 期望最終結(jié)果應(yīng)該是 0
        System.out.println(counter.value());
    }
}

大家看結(jié)果:

大家觀察下是否適用多線程的現(xiàn)象是否一致?同時(shí)嘗試思考下為什么會有這樣的現(xiàn)象發(fā)生呢?

想給出一個(gè)線程安全的確切定義是復(fù)雜的,但我們可以這樣認(rèn)為:

如果多線程環(huán)境下代碼運(yùn)行的結(jié)果是符合我們預(yù)期的,即在單線程環(huán)境應(yīng)該的結(jié)果,則說這個(gè)程序是線程安全的。

1.2 不安全的原因

1.2.1 原子性

舉個(gè)簡單的例子,當(dāng)我i們買票的時(shí)候,如果車站剩余票數(shù)大于0,就可以買。反之,買完一張票后,車站的票數(shù)也會自動(dòng)減一。假設(shè)出現(xiàn)這種情況,兩個(gè)人同時(shí)來買票,只剩最后一張票,前面那個(gè)人把最后一張票買了,但是短時(shí)間內(nèi)票數(shù)還沒減一也就是清零,這時(shí)另外一個(gè)人看到還有一張票,于是提交訂單,但是其實(shí)已經(jīng)沒有多余的票了,那么問題就來了。這時(shí)我們引入原子性:

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

1.2.2 代碼“優(yōu)化”

一段代碼是這樣的:

1. 去前臺取下 U 盤

2. 去教室寫 10 分鐘作業(yè)

3. 去前臺取下快遞

如果是在單線程情況下, JVM 、 CPU 指令集會對其進(jìn)行優(yōu)化,比如,按 1->3->2 的方式執(zhí)行,也是沒問題,可以少跑 一次前臺。這種叫做指令重排序。 剛才那個(gè)例子中,單線程情況是沒問題的,優(yōu)化是正確的,但在多線程場景下就有問題了,什么問題呢??赡芸爝f是 在你寫作業(yè)的10 分鐘內(nèi)被另一個(gè)線程放過來的,或者被人變過了,如果指令重排序了,代碼就會是錯(cuò)誤的。

二:如何解決線程不安全的問題

2.1 通過synchronized關(guān)鍵字

synchronized 的底層是使用操作系統(tǒng)的 mutex lock 實(shí)現(xiàn)的。 當(dāng)線程釋放鎖時(shí), JMM 會把該線程對應(yīng)的工作內(nèi)存中的共享變量刷新到主內(nèi)存中 當(dāng)線程獲取鎖時(shí), JMM 會把該線程對應(yīng)的本地內(nèi)存置為無效。從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須從主內(nèi) 存中讀取共享變量 synchronized 用的鎖是存在 Java對象頭里的。 synchronized 同步快對同一條線程來說是可重入的,不會出現(xiàn)自己把自己鎖死的問題; 同步塊在已進(jìn)入的線程執(zhí)行完之前,會阻塞后面其他線程的進(jìn)入。

鎖的 SynchronizedDemo 對象

public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method();
// 進(jìn)入方法會鎖 SynchronizedDemo.class 指向?qū)ο笾械逆i;出方法會釋放
SynchronizedDemo.class 指向的對象中的鎖
}
}

鎖的 SynchronizedDemo 類的對象

public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method();
// 進(jìn)入方法會鎖 SynchronizedDemo.class 指向?qū)ο笾械逆i;出方法會釋放
SynchronizedDemo.class 指向的對象中的鎖
}
}

明確鎖的對象

public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method();
// 進(jìn)入方法會鎖 SynchronizedDemo.class 指向?qū)ο笾械逆i;出方法會釋放
SynchronizedDemo.class 指向的對象中的鎖
}
}
public class SynchronizedDemo {
public void methond() {
// 進(jìn)入代碼塊會鎖 SynchronizedDemo.class 指向?qū)ο笾械逆i;出代碼塊會釋放
SynchronizedDemo.class 指向的對象中的鎖
synchronized (SynchronizedDemo.class) {
}
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.method();
}
}

2.2 volatile

這里提一下volatile:

首先,被volatile關(guān)鍵字修飾的變量,編譯器與運(yùn)行時(shí)都會注意到這個(gè)變量是共享的,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時(shí)總會返回最新寫入的值。

在訪問volatile變量時(shí)不會執(zhí)行加鎖操作,因此也就不會使執(zhí)行線程阻塞,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級的同步機(jī)制。當(dāng)對非 volatile 變量進(jìn)行讀寫的時(shí)候,每個(gè)線程先從內(nèi)存拷貝變量到CPU緩存中。如果計(jì)算機(jī)有多個(gè)CPU,每個(gè)線程可能在不同的CPU上被處理,這意味著每個(gè)線程可以拷貝到不同的 CPU cache 中。而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過 CPU cache 這一步

三:wait和notify關(guān)鍵字

3.1 wait方法

其實(shí) wait() 方法就是使線程停止運(yùn)行。

1. 方法 wait() 的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待, wait() 方法是 Object 類的方法,該方法是用來將當(dāng)前線程 置入 “ 預(yù)執(zhí)行隊(duì)列 ” 中,并且在 wait() 所在的代碼處停止執(zhí)行,直到接到通知或被中斷為止。

2. wait() 方法只能在同步方法中或同步塊中調(diào)用。如果調(diào)用 wait() 時(shí),沒有持有適當(dāng)?shù)逆i,會拋出異常。

3. wait() 方法執(zhí)行后,當(dāng)前線程釋放鎖,線程與其它線程競爭重新獲取鎖。

public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println(" 等待中 ...");
object.wait();
System.out.println(" 等待已過 ...");
}
System.out.println("main 方法結(jié)束 ...");
}

這樣在執(zhí)行到 object.wait() 之后就一直等待下去,那么程序肯定不能一直這么等待下去了。這個(gè)時(shí)候就需要使用到了 另外一個(gè)方法喚醒的方法 notify() 。

3.2 notify方法

notify 方法就是使停止的線程繼續(xù)運(yùn)行。

  • 1. 方法 notify() 也要在同步方法或同步塊中調(diào)用,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對 其發(fā)出通知 notify ,并使它們重新獲取該對象的對象鎖。如果有多個(gè)線程等待,則有線程規(guī)劃器隨機(jī)挑選出一個(gè) 呈 wait 狀態(tài)的線程。
  • 2. 在 notify() 方法后,當(dāng)前線程不會馬上釋放該對象鎖,要等到執(zhí)行 notify() 方法的線程將程序執(zhí)行完,也就是退出 同步代碼塊之后才會釋放對象鎖。
class MyThread implements Runnable {
    private boolean flag;
    private Object obj;
    public MyThread(boolean flag, Object obj) {
        super();
        this.flag = flag;
        this.obj = obj;
    }
    public void waitMethod() {
        synchronized (obj) {
            try {
                while (true) {
                    System.out.println("wait()方法開始.. " +
                            Thread.currentThread().getName());
                    obj.wait();
                    System.out.println("wait()方法結(jié)束.. " +
                            Thread.currentThread().getName());
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public void notifyMethod() {
        synchronized (obj) {
            try {
                System.out.println("notifyAll()方法開始.. " +
                        Thread.currentThread().getName());
                obj.notifyAll();
                System.out.println("notifyAll()方法結(jié)束.. " +
                        Thread.currentThread().getName());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        if (flag) {
            this.waitMethod();
        } else {
            this.notifyMethod();
        }
    }
}
public class TestThread {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MyThread waitThread1 = new MyThread(true, object);
        MyThread waitThread2 = new MyThread(true, object);
        MyThread waitThread3 = new MyThread(true, object);
        MyThread notifyThread = new MyThread(false, object);
        Thread thread1 = new Thread(waitThread1, "wait線程A");
        Thread thread2 = new Thread(waitThread2, "wait線程B");
        Thread thread3 = new Thread(waitThread3, "wait線程C");
        Thread thread4 = new Thread(notifyThread, "notify線程");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
        thread4.start();
        System.out.println("main方法結(jié)束!!");
    }
}

從結(jié)果上來看第一個(gè)線程執(zhí)行的是一個(gè) waitMethod 方法,該方法里面有個(gè)死循環(huán)并且使用了 wait 方法進(jìn)入等待狀態(tài) 將釋放鎖,如果這個(gè)線程不被喚醒的話將會一直等待下去,這個(gè)時(shí)候第二個(gè)線程執(zhí)行的是 notifyMethod 方法,該方 法里面執(zhí)行了一個(gè)喚醒線程的操作,并且一直將 notify 的同步代碼塊執(zhí)行完畢之后才會釋放鎖然后繼續(xù)執(zhí)行 wait 結(jié)束 打印語句。 注意: wait , notify 必須使用在 synchronized 同步方法或者代碼塊內(nèi)。

3.3 wait和sleep對比(面試??迹?/h3>

其實(shí)理論上 wait 和 sleep 完全是沒有可比性的,因?yàn)橐粋€(gè)是用于線程之間的通信的,一個(gè)是讓線程阻塞一段時(shí)間, 唯一的相同點(diǎn)就是都可以讓線程放棄執(zhí)行一段時(shí)間。用生活中的例子說的話就是婚禮時(shí)會吃糖,和家里自己吃糖之間 有差別。說白了放棄線程執(zhí)行只是 wait 的一小段現(xiàn)象。 當(dāng)然為了面試的目的,我們還是總結(jié)下:

  • 1. wait 之前需要請求鎖,而 wait 執(zhí)行時(shí)會先釋放鎖,等被喚醒時(shí)再重新請求鎖。這個(gè)鎖是 wait 對像上的 monitor 
  • lock
  • 2. sleep 是無視鎖的存在的,即之前請求的鎖不會釋放,沒有鎖也不會請求。
  • 3. wait 是 Object 的方法
  • 4. sleep 是 Thread 的靜態(tài)方法

四:多線程案例

4.1 餓漢模式單線程

class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}

4.2 懶漢模式單線程

class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

4.3 懶漢模式多線程低性能版

class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

4.4懶漢模式-多線程版-二次判斷-性能高

class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

總結(jié)

多線程的部分暫時(shí)分享到這里,但其實(shí)還有很多沒有沒有涉及 ,等日后深刻理解后再來分享,碼文不易,多謝大家支持,感激不盡!

到此這篇關(guān)于Java多線程之搞定最后一公里詳解的文章就介紹到這了,更多相關(guān)Java 多線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot實(shí)現(xiàn)微信小程序支付功能

    SpringBoot實(shí)現(xiàn)微信小程序支付功能

    小程序支付功能已成為眾多應(yīng)用的核心需求之一,本文主要介紹了SpringBoot實(shí)現(xiàn)微信小程序支付功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • 如何在JAVA中使用Synchronized

    如何在JAVA中使用Synchronized

    這篇文章主要介紹了如何在JAVA中使用Synchronized,文中代碼非常詳細(xì),對大家的學(xué)習(xí)有所幫助,感興趣的朋友可以參考下
    2020-06-06
  • Nacos服務(wù)發(fā)現(xiàn)并發(fā)啟動(dòng)scheduleUpdate定時(shí)任務(wù)的流程分析

    Nacos服務(wù)發(fā)現(xiàn)并發(fā)啟動(dòng)scheduleUpdate定時(shí)任務(wù)的流程分析

    這篇文章主要介紹了Nacos服務(wù)發(fā)現(xiàn)并發(fā)啟動(dòng)scheduleUpdate定時(shí)任務(wù),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • Java中靜態(tài)類型檢查是如何進(jìn)行的實(shí)例思路詳解

    Java中靜態(tài)類型檢查是如何進(jìn)行的實(shí)例思路詳解

    這篇文章主要介紹了Java中靜態(tài)類型檢查是如何進(jìn)行的實(shí)例思路詳解的相關(guān)資料,需要的朋友可以參考下
    2016-05-05
  • spring使用Filter過濾器對Response返回值進(jìn)行修改的方法

    spring使用Filter過濾器對Response返回值進(jìn)行修改的方法

    這篇文章主要介紹了spring使用Filter過濾器對Response返回值進(jìn)行修改,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • java如何獲取兩個(gè)List集合之間的交集、差集、并集

    java如何獲取兩個(gè)List集合之間的交集、差集、并集

    在日常開發(fā)中經(jīng)常會遇到對2個(gè)集合的操作,例如2個(gè)集合之間取相同的元素(交集),2個(gè)集合之間取不相同的元素(差集)等等,這篇文章主要給大家介紹了關(guān)于java如何獲取兩個(gè)List集合之間的交集、差集、并集的相關(guān)資料,需要的朋友可以參考下
    2024-02-02
  • 解決idea中yml文件不識別的問題

    解決idea中yml文件不識別的問題

    這篇文章主要介紹了解決idea中yml文件不識別的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • SpringBoot的@Value注解如何設(shè)置默認(rèn)值

    SpringBoot的@Value注解如何設(shè)置默認(rèn)值

    這篇文章主要介紹了SpringBoot的@Value注解如何設(shè)置默認(rèn)值問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Springboot項(xiàng)目啟動(dòng)優(yōu)化方式

    Springboot項(xiàng)目啟動(dòng)優(yōu)化方式

    文章詳細(xì)介紹了Spring Boot項(xiàng)目的啟動(dòng)優(yōu)化策略,包括懶加載、異步初始化、精簡依賴、JVM優(yōu)化和使用Actuator監(jiān)控等方法,旨在提高項(xiàng)目的啟動(dòng)速度和運(yùn)行性能
    2025-03-03
  • Maven包沖突導(dǎo)致NoSuchMethodError錯(cuò)誤的解決辦法

    Maven包沖突導(dǎo)致NoSuchMethodError錯(cuò)誤的解決辦法

    web 項(xiàng)目 能正常編譯,運(yùn)行時(shí)也正常啟動(dòng),但執(zhí)行到需要調(diào)用 org.codehaus.jackson 包中的某個(gè)方法時(shí),產(chǎn)生運(yùn)行異常,這篇文章主要介紹了Maven包沖突導(dǎo)致NoSuchMethodError錯(cuò)誤的解決辦法,需要的朋友可以參考下
    2024-05-05

最新評論

贺兰县| 博白县| 石河子市| 聂荣县| 科尔| 淳化县| 青田县| 吉林市| 沿河| 得荣县| 英超| 治县。| 德昌县| 浦江县| 永城市| 山东| 霸州市| 建瓯市| 崇仁县| 宜黄县| 丽水市| 怀安县| 平度市| 左权县| 霍州市| 巴青县| 兴安县| 高州市| 寿宁县| 浑源县| 华阴市| 康平县| 宣化县| 沂源县| 克拉玛依市| 敖汉旗| 泸州市| 略阳县| 武川县| 赣榆县| 江阴市|