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