時間:2017年07月09日星期日
說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com
教學源碼:無
學習源碼:https://github.com/zccodere/s...
課程目標和學習內容
共享變量在線程間的可見性 synchronized實現可見性 volatile實現可見性 指令重排序 as-if-serial語義 volatile使用注意事項 synchronized和volatile比較第二章:可見性介紹 2-1 可見性介紹
可見性
一個線程對共享變量值的修改,能夠及時地被其他線程看到
共享變量
如果一個變量在多個線程的工作內存中都存在副本,那么這個變量就是這幾個線程的共享變量
Java內存模型(JMM)
Java內存模型(Java Memory Model)描述了Java程序中各種變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的底層細節
Java內存模型
所有的變量都存儲在主內存中 每個線程都有自己獨立的工作內存,里面保存該線程使用到的變量的副本 (主內存中該變量的一份拷貝)
Java內存模型示意圖
Java內存模型中的兩條規定
線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接從主內存中讀寫 不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成
共享變量可見性實現的原理。如線程1對共享變量的修改要想被線程2及時看到,必須要經過如下2個步驟
把工作內存1中更新過的共享變量刷新到主內存中 將主內存中最新的共享變量的值更新到工作內存2中
共享變量可見性實現的原理示意圖
第三章:synchronized實現可見性 3-1 synchronized實現可見性原理要實現共享變量的可見性,必須保證兩點
線程修改后的共享變量能夠及時從工作內存刷新到主內存中 其他線程能夠及時把共享變量的最新值從主內存更新到自己的工作內存中
Java語言層面支持的可見性實現方式
synchronized volatile
synchronized能夠實現
原子性(同步) 可見性
JMM關于synchronized的兩條規定
線程解鎖前,必須把共享變量的最新值刷新到主內存中 線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值 (注意:加鎖與解鎖需要時同一把鎖)
線程解鎖前對共享變量的修改在下次加鎖時對其他線程可見
線程執行互斥代碼的過程
1.獲得互斥鎖 2.清空工作內存 3.從主內存拷貝變量的最新副本到工作內存 4.執行代碼 5.將更改后的共享變量的值刷新到主內存 6.釋放互斥鎖
重排序
代碼書寫的順序與實際執行的順序不同,指令重排序是編譯器或處理器為了提高程序性能而做的優化
當前的三種重排序
編譯器優化的重排序(編譯器優化) 指令級并行重排序(處理器優化) 內存系統的重排序(處理器優化)
重排序示意圖:有可能出現下面情況
as-if-serial
無論如何重排序,程序執行的結果應該與代碼順序執行的結果一致(Java編譯器、運行時和處理器都會保證Java在單線程下遵循as-if-serial語義)3-2 synchronized實現可見性(上)
代碼演示:
package com.myimooc.synchronizeddemo.my; /** * 程序主類 * @author ZhangCheng on 2017-07-09 * */ public class SynchronizedDemo { //共享變量 private boolean ready = false; private int result = 0; private int number = 1; //寫操作 public synchronized void write(){ ready = true; //1.1 number = 2; //1.2 } //讀操作 public synchronized void read(){ if(ready){ //2.1 result = number*3; //2.2 } System.out.println("result的值為:" + result); } //內部線程類 private class ReadWriteThread extends Thread { //根據構造方法中傳入的flag參數,確定線程執行讀操作還是寫操作 private boolean flag; public ReadWriteThread(boolean flag){ this.flag = flag; } @Override public void run() { if(flag){ //構造方法中傳入true,執行寫操作 write(); }else{ //構造方法中傳入false,執行讀操作 read(); } } } public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); //啟動線程執行寫操作 synDemo.new ReadWriteThread(true).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //啟動線程執行讀操作 synDemo.new ReadWriteThread(false).start(); } }3-3 synchronized實現可見性(中)
導致共享變量在線程間不可見的原因
線程的交叉執行 重排序結合線程交叉執行 共享變量更新后的值沒有在工作內存與主內存間及時更新
安全的代碼
3-4 synchronized實現可見性(下) 第四章:volatile實現可見性 4-1 volatile能夠保證可見性volatile關鍵字
能夠保證volatile變量的可見性 不能保證volatile變量復合操作的原子性
volatile如何實現內存可見性
深入來說:通過加入內存屏障和禁止重排序優化來實現的 對volatile變量執行寫操作時,會在寫操作后加入一個store屏障指令 對volatile變量執行讀操作時,會在讀操作前加入一條load屏障指令 通俗地講:volatile變量在每次被線程訪問時,都強迫從主內存中重讀該變量的值, 而當該變量發生變化時,又會強迫線程將最新的值刷新到主內存。 這樣任何時刻,不同的線程總能看到該變量的最新值
線程寫volatile變量的過程
改變線程工作內存中volatile變量副本的值 將改變后的副本的值從工作內存刷新到主內存
線程讀volatile變量的過程
從主內存中讀取volatile變量的最新值到線程的工作內存中 從工作內存中讀取volatile變量的副本4-2 volatile不能保證原子性(上)
代碼演示:
package com.myimooc.volatiledemo.my; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 程序主類 * @author ZhangCheng on 2017-07-09 * */ public class VolatileDemo { private int number = 0; public int getNumber(){ return this.number; } public void increase(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } this.number++; } public static void main(String[] args) { final VolatileDemo volDemo = new VolatileDemo(); for(int i = 0 ; i < 500 ; i++){ new Thread(new Runnable() { @Override public void run() { volDemo.increase(); } }).start(); } //如果還有子線程在運行,主線程就讓出CPU資源, //直到所有的子線程都運行完了,主線程再繼續往下執行 while(Thread.activeCount() > 1){ Thread.yield(); } System.out.println("number : " + volDemo.getNumber()); } }4-3 volatile不能保證原子性(中)
程序分析
解決方案:保證number自增操作的原子性
使用synchronized關鍵字 使用ReentrantLock(java.util.concurrent.locks包下) 使用AtomicInterger(java.util.concurrent.atomic包下)4-4 volatile不能保證原子性(下)
代碼演示:
package com.myimooc.volatiledemo.my; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 程序主類 * @author ZhangCheng on 2017-07-09 * */ public class VolatileDemo { private int number = 0; private Lock lock = new ReentrantLock(); public int getNumber(){ return this.number; } public void increase(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 方案一 /* synchronized(this){ this.number++; } */ // 方案二 lock.lock();// 獲取鎖 try { this.number++; } finally { lock.unlock();// 釋放鎖 } } public static void main(String[] args) { final VolatileDemo volDemo = new VolatileDemo(); for(int i = 0 ; i < 500 ; i++){ new Thread(new Runnable() { @Override public void run() { volDemo.increase(); } }).start(); } //如果還有子線程在運行,主線程就讓出CPU資源, //直到所有的子線程都運行完了,主線程再繼續往下執行 while(Thread.activeCount() > 1){ Thread.yield(); } System.out.println("number : " + volDemo.getNumber()); } }4-5 volatile使用注意事項
volatile適用場合:要在多線程中安全的使用volatile變量,必須同時滿足
對變量的寫入操作不依賴其當前值 不滿足:number++、count = count * 5等 滿足:boolean變量、記錄溫度變化的變量等 該變量沒有包含在具有其他變量的不變式中 不滿足:不變式low < up4-6 synchronized和volatile比較
synchronized和volatile比較
volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程 從內存可見性角度講,volatile讀相當于加鎖,volatile寫相當于解鎖 synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性第五章:課程總結 5-1 課程總結
課程總結
什么是內存可見性 Java內存模型(JMM) 實現可見性的方式:synchronized和volatile final也可以保證內存可見性 synchronized和volatile實現內存可見性的原理 synchronized實現可見性 指令重排序 as-if-serial語義 volatile實現可見性 volatile能夠保證可見性 volatile不能保證原子性 volatile使用注意事項 synchronized和volatile比較 volatile比synchronized更輕量級 volatile沒有synchronized使用的廣泛
問:即使沒有保證可見性的措施,很多時候共享變量依然能夠在主內存和工作內存見得到及時地更新?
答:一般只有在短時間內高并發的情況下才會出現變量得不到及時更新的情況,因為CPU在執行時會很快的刷新緩存,所以一般情況下很難看到這種情況。
對64位(long、double)變量的讀寫可能不是原子操作
Java內存模型允許JVM將沒有被volatile修飾的64位數據類型的讀寫操作劃分為兩次32位的讀寫操作來進行 導致問題:有可能會出現讀取到“半個變量”的情況 解決辦法:加volatile關鍵字
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/67326.html
摘要:時間年月日星期六說明本文部分內容均來自慕課網。慕課網教學源碼無學習源碼第一章課前準備前言課程說明比較和這兩種線程創建的方式,需要知道和的基本創建方式。一旦主線程獲取到了用戶的輸入,這時候,阻塞就會解除掉,主線程繼續運行,直到結束。 時間:2017年07月08日星期六說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學源碼:無學習源碼:https://g...
時間:2018年04月11日星期三 說明:本文部分內容均來自慕課網。@慕課網:https://www.imooc.com 教學源碼:https://github.com/zccodere/s... 學習源碼:https://github.com/zccodere/s... 第一章:課程介紹 1-1 課程介紹 什么是Netty 高性能、事件驅動、異步非阻塞的IO Java開源框架 基于NIO的客戶...
時間:2017年05月24日星期三說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學示例源碼:無個人學習源碼:https://github.com/zccodere/s... 第一章:課程介紹 1-1 課程介紹 什么是定時任務調度 基于給定的時間點,給定的時間間隔或者給定的執行次數自動執行的任務 在Java中的定時調度工具 Timer:小弟,能實現日常60%的定...
時間:2017年10月16日星期一說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學源碼:無學習源碼:https://github.com/zccodere/s... 第一章:課程簡介 1-1 課程介紹 本門課程的主要內容 RxJava是什么 RxAndroid是什么 RxJava常用操作符(重點、難點) 怎樣在項目中使用RxJava和RxAndroid 如何學...
時間:2017年07月11日星期二說明:本文部分內容均來自慕課網。@慕課網:http://www.imooc.com教學源碼:無學習源碼:https://github.com/zccodere/s... 第一章:應用場景 1-1 多對多的應用場景 案例分析:企業項目開發過程中 一個項目可由多個員工參與開發 一個員工可同時參與開發多個項目 示意圖 showImg(https://segmentfau...
閱讀 3086·2023-04-26 00:53
閱讀 3536·2021-11-19 09:58
閱讀 1700·2021-09-29 09:35
閱讀 3290·2021-09-28 09:46
閱讀 3869·2021-09-22 15:38
閱讀 2697·2019-08-30 15:55
閱讀 3016·2019-08-23 14:10
閱讀 3830·2019-08-22 18:17