国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Week 1 - Java 多線程 - Java 內存模型

zhaofeihao / 2847人閱讀

摘要:目的是解決由于多線程通過共享內存進行通信時,存在的原子性可見性緩存一致性以及有序性問題。最多只有一個線程能持有鎖。線程加入規則對象的結束先行發生于方法返回。

前言

學習情況記錄

時間:week 1

SMART子目標 :Java 多線程

學習Java多線程,要了解多線程可能出現的并發現象,了解Java內存模型的知識是必不可少的。

對學習到的重要知識點進行的記錄。

注:這里提到的是Java內存模型,是和并發編程相關的,不是JVM內存結構(堆、方法棧這些概念),這兩個不是一回事,別弄混了。

Java 內存模型
Java內存模型(Java Memory Model ,JMM)就是一種符合內存模型規范的,屏蔽了各種硬件和操作系統的訪問差異的,保證了Java程序在各種平臺下對內存的訪問都能得到一致效果的機制及規范。目的是解決由于多線程通過共享內存進行通信時,存在的原子性、可見性(緩存一致性)以及有序性問題。
主內存與工作內存

先看計算機硬件的緩存訪問操作:

? 處理器上的寄存器的讀寫的速度比內存快幾個數量級,為了解決這種速度矛盾,在它們之間加入了高速緩存。

? 加入高速緩存帶來了一個新的問題:緩存一致性。如果多個緩存共享同一塊主內存區域,那么多個緩存的數據可能會不一致,需要一些協議來解決這個問題。

Java的內存訪問操作與上述的硬件緩存具有很高的可比性:

? Java內存模型中,規定了所有的變量都存儲在主內存中,每個線程還有自己的工作內存,工作內存存儲在高速緩存或者寄存器中,保存了該線程使用的變量的主內存副本拷貝。線程只能直接操作工作內存中的變量,不同線程之間的變量值傳遞需要通過主內存來完成。

內存間交互操作

Java 內存模型定義了 8 個操作來完成主內存和工作內存的交互操作

read:把一個變量的值從主內存傳輸到線程的工作內存中

load:在 read 之后執行,把 read 得到的值放入線程的工作內存的變量副本中

use:把線程的工作內存中一個變量的值傳遞給執行引擎

assign:把一個從執行引擎接收到的值賦給工作內存的變量

store:把工作內存的一個變量的值傳送到主內存中

write:在 store 之后執行,把 store 得到的值放入主內存的變量中

lock:作用于主內存的變量,把一個變量標識成一條線程獨占的狀態

unlock: 作用于主內存的變量,把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定。

內存模型三大特性 原子性

Java 內存模型保證了 read、loaduse、assign、store、write、lockunlock 操作具有原子性,例如對一個 int 類型的變量執行 assign 賦值操作,這個操作就是原子性的。但是 Java 內存模型允許虛擬機將沒有被 volatile 修飾的 64 位數據(longdouble)的讀寫操作劃分為兩次 32 位的操作來進行,也就是說基本數據類型的訪問讀寫是原子性的,除了longdouble是非原子性的,load、storeread write 操作可以不具備原子性。書上提醒我們只需要知道有這么一回事,因為這個是幾乎不可能存在的例外情況。

雖然上面說對基本數據類型的訪問讀寫是原子性的,但是不代表在多線程環境中,如int類型的變量不會出現線程安全問題。詳細的例子可以參考范例一。

想要保證原子性,可以嘗試以下幾種方式:

如果是基礎類型的變量的話,使用Atomic類(例如AtomicInteger)

其他情況下,可以使用synchronized互斥鎖來保證 限定臨界區 內操作的原子性。它對應的內存間交互操作為:lock 和 unlock,在虛擬機實現上對應的字節碼指令為 monitorenter 和 monitorexit

可見性

可見性指的是,當一個線程修改了共享變量中的值,其他線程能夠立即得知這個修改。Java 內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值來實現可見性的。

可見性的錯誤問題范例比較難以模擬,有興趣的可以借助此篇文章更好的理解。

想要保證可見性,主要有三種實現方式:

volatile

Java的內存分主內存和線程工作內存,volatile保證修改立即由當前線程工作內存同步到主內存,但其他線程仍需要從主內存取才能保證線程同步。

synchronized

當線程獲取鎖時會從主內存中獲取共享變量的最新值,釋放鎖的時候會將共享變量同步到主內存中。最多只有一個線程能持有鎖。

final

被 final 關鍵字修飾的字段在構造器中一旦初始化完成,并且沒有發生 this 逃逸(其它線程通過 this 引用訪問到初始化了一半的對象),那么其它線程就能看見 final 字段的值。

范例一中的 cnt 變量使用 volatile 修飾,不能解決線程不安全問題,因為 volatile 并不能保證操作的原子性。

有序性

有序性是指:在本線程內觀察,所有操作都是有序的。在一個線程觀察另一個線程,所有操作都是無序的,無序是因為發生了指令重排序。在 Java 內存模型中,允許編譯器和處理器對指令進行重排序,重排序過程不會影響到單線程程序的執行,卻會影響到多線程并發執行的正確性。

想要保證可見性,主要以下實現方式:

volatile

volatile的真正意義在于產生內存屏障,禁止指令重排序。即重排序時不能把后面的指令放到內存屏障之前。

synchronized

它保證每個時刻只有一個線程執行同步代碼,相當于是讓線程順序執行同步代碼。

有序性這塊比較難比較深的內容實際上是指令重排序這塊的知識。我這就借花獻佛,引一篇我認為講的比較清楚的文章。內存模型之重排序

先行發生原則

JVM 內存模型下,規定了先行發生原則,讓一個操作無需任何同步器協助就能先于另一個操作完成。如果兩個操作之間的關系不在此列,并且無法從下列規則推導出來的話,它們就沒有順序性保障,虛擬機可以對他們隨意的進行重排序。

單一線程規則 - Single Thread Rule

在其他書上又叫 Program Order Rule - 程序次序規則

在一個線程中, 在線程前面的操作先行發生于后面的操作。(準確的來說,是控制流順序,而不是代碼順序,因為或有邏輯判斷分支)

管道鎖定規則 - Monitor Lock Rule

一個 unlock 操作先行發生于后面對同一個鎖的 lock 操作。

volatile 變量規則 - Volatile Variable Rule

對一個volatile 變量的寫操作先行發生于 后面對這個變量的讀操作

線程啟動規則 - Thread Start Rule

Thread 對象的 start() 方法調用先行發生于此線程的每一個動作。

線程加入規則 - Thread Join Rule

Thread 對象的結束先行發生于 join() 方法返回。

線程中斷規則 - Thread Interruption Rule

對線程 interrupt() 方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過 interrupted() 方法檢測到是否有中斷發生。

對象終結規則- Finalizer Rule

一個對象的初始化完成(構造函數執行結束)先行發生于它的 finalize() 方法的開始。

傳遞性 - Transitivity

如果操作 A 先行發生于操作 B,操作 B 先行發生于操作 C,那么操作 A 先行發生于操作 C。

在多線程情況下,時間先后順序和先行發生原則之間基本沒有太大的關系,我們衡量并發安全問題的時候不要受到時間順序的告饒,一切必須以先行發生原則為準。

插入案例幫助理解 案例一 代碼
/**
 * 內存模型三大特性 - 原子性驗證對比
 *
 * @author Richard_yyf
 * @version 1.0 2019/7/2
 */
public class AtomicExample {

    private static AtomicInteger atomicCount = new AtomicInteger();

    private static int count = 0;

    private static void add() {
        atomicCount.incrementAndGet();
        count++;
    }

    public static void main(String[] args) {
        final int threadSize = 1000;
        final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < threadSize; i++) {
            executor.execute(() -> {
                add();
                countDownLatch.countDown();
            });
        }
        System.out.println("atomicCount: " + atomicCount);
        System.out.println("count: " + count);

        ThreadPoolUtil.tryReleasePool(executor);
    }
}
Outout
atomicCount: 1000
count: 997
分析

可以借助下圖幫助理解。

count++這個簡單的操作根據上面的原理分析,可以知道內存操作實際分為讀寫存三步;因為讀寫存這個整體的操作,不具備原子性,count被兩個或多個線程讀入了同樣的舊值,讀到線程內存當中,再進行寫操作,再存回去,那么就可能出現主內存被重復set同一個值的情況,如上圖所示,兩個線程進行了count++,實際上只進行了一次有效操作。

案例二 代碼
class Foo {
    private int x = 100;

    public int getX() {
        return x;
    } 

    public int fix(int y) {
        x = x - y; 
        return x;
    } 
}


 public class MyRunnable implements Runnable {
    private Foo foo =new Foo(); 

    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread ta = new Thread(r,"Thread-A"); 
        Thread tb = new Thread(r,"Thread-B"); 
        ta.start(); 
        tb.start(); 
    } 

    public  void run() {
        
        for (int i = 0; i < 3; i++) {
            this.fix(30);
            try {
                Thread.sleep(1); 
            } catch (InterruptedException e) {
                e.printStackTrace(); 
            } 
            System.out.println(Thread.currentThread().getName() + " :當前foo對象的x值= " + foo.getX());
        } 
    } 

    public int fix(int y) {
        return foo.fix(y);
    } 
}
Output
Thread-A:當前foo對象的的x值= 70
Thread-B:當前foo對象的的x值= 70
Thread-A:當前foo對象的的x值= 10
Thread-B:當前foo對象的的x值= 10
Thread-A:當前foo對象的的x值= -50
Thread-B:當前foo對象的的x值= -50
分析

這個案例是案例一的變體,只是代碼有點復雜有點繞而已,實際上就是存在兩個線程,對一個實例的共享變量進行-30的操作。

read 的操作發生在x-y的x處,相當于兩個線程第一次fix(30)的時候,對x變量做了兩次100-30的賦值操作。

案例三
public class Test {
       // 是否是原子性?
    int i = 1;
    public static void main(String[] args) {
        Test test = new Test();
    }
}

請問上述 int i = 1是否是原子性的呢?

實際上很微妙。

本案例中的int a = 1在java中叫顯式初始化,它實際上包含兩次賦值,第一次java自動將a初始化為0,第二次再賦值為1。從這個角度看,這條語句包含了兩步操作,并不是原子的。

但是由于這句代碼是在構造方法中,而從類的實例化角度看,一般認為構造方法中對當前實例的初始化過程是原子的。這是因為在實例化完成之前,一般是無法從別的代碼中訪問到當前實例的。所以從這個角度看,int a = 1實際上是原子的。

參考

《深入理解Java虛擬機》

https://juejin.im/post/5bd971...

http://ifeve.com/concurrency-...

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75208.html

相關文章

  • Week 1 - Java 線程 - CAS

    摘要:前言學習情況記錄時間子目標多線程記錄在學習線程安全知識點中,關于的有關知識點。對于資源競爭嚴重線程沖突嚴重的情況,自旋的概率會比較大,從而浪費更多的資源,效率低于。 前言 學習情況記錄 時間:week 1 SMART子目標 :Java 多線程 記錄在學習線程安全知識點中,關于CAS的有關知識點。 線程安全是指:多個線程不管以何種方式訪問某個類,并且在主調代碼中不需要進行同步,都能表...

    ZweiZhao 評論0 收藏0
  • Week 2 - Java 容器 - 詳細剖析 List 之 ArrayList, Vector,

    摘要:底層使用的是雙向鏈表數據結構之前為循環鏈表,取消了循環??焖匐S機訪問就是通過元素的序號快速獲取元素對象對應于方法。而接口就是用來標識該類支持快速隨機訪問。僅僅是起標識作用。,中文名為雙端隊列。不同的是,是線程安全的,內部使用了進行同步。 前言 學習情況記錄 時間:week 2 SMART子目標 :Java 容器 記錄在學習Java容器 知識點中,關于List的需要重點記錄的知識點。...

    MartinDai 評論0 收藏0
  • 來,了解一下Java內存模型(JMM)

    摘要:因為管理人員是了解手下的人員以及自己負責的事情的。處理器優化和指令重排上面提到在在和主存之間增加緩存,在多線程場景下會存在緩存一致性問題。有沒有發現,緩存一致性問題其實就是可見性問題。 網上有很多關于Java內存模型的文章,在《深入理解Java虛擬機》和《Java并發編程的藝術》等書中也都有關于這個知識點的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說自己更懵了。本文,就來整體的...

    kviccn 評論0 收藏0
  • 來,了解一下Java內存模型(JMM)

    摘要:因為管理人員是了解手下的人員以及自己負責的事情的。處理器優化和指令重排上面提到在在和主存之間增加緩存,在多線程場景下會存在緩存一致性問題。有沒有發現,緩存一致性問題其實就是可見性問題。 網上有很多關于Java內存模型的文章,在《深入理解Java虛擬機》和《Java并發編程的藝術》等書中也都有關于這個知識點的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說自己更懵了。本文,就來整體的...

    eccozhou 評論0 收藏0

發表評論

0條評論

zhaofeihao

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<