Java線程安全以及線程安全的實現方式和內存模型(JMM)

一、了解幾個概念?

1)臨界區:

?臨界區指的是一個訪問共用資源(例如:共用設備或是共用存儲器)的程序片段,而這些共用資源又無法同時被多個線程訪問的特性。當有線程進入臨界區段時,其他線程或是進程必須等待,有一些同步的機制必須在臨界區段的進入點與離開點實現,以確保這些共用資源是被互斥獲得使用?

?

2)互斥量:

互斥量是一個可以處于兩態之一的變量:解鎖和加鎖。這樣,只需要一個二進制位表示它,不過實際上,常常使用一個整型量,0表示解鎖,而其他所有的值則表示加鎖。互斥量使用兩個過程。當一個線程(或進程)需要訪問臨界區時,它調用mutex_lock。如果該互斥量當前是解鎖的(即臨界區可用),此調用成功,調用線程可以自由進入該臨界區。
另一方面,如果該互斥量已經加鎖,調用線程被阻塞,直到在臨界區中的線程完成并調用mutex_unlock。如果多個線程被阻塞在該互斥量上,將隨機選擇一個線程并允許它獲得鎖。?

?

3)信號量:

信號量(Semaphore),有時被稱為信號燈,是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被并發調用。在進入一個關鍵代碼段之前,線程必須獲取一個信號量;一旦該關鍵代碼段完成了,那么該線程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待直到第一個線程釋放信號量。為了完成這個過程,需要創建一個信號量VI,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個關鍵代碼段的首末端。確認這些信號量VI引用的是初始創建的信號量。?

?

4)CAS操作(Compare-and-Swap):

CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B

?

5)重排序:

編譯器和處理器”為了提高性能,而在程序執行時會對程序進行的重排序。它的出現是為了提高程序的并發度,從而提高性能!但是對于多線程程序,重排序可能會導致程序執行的結果不是我們需要的結果!重排序分為“編譯器”和“處理器”兩個方面,而“處理器”重排序又包括“指令級重排序”和“內存的重排序”

?

?

?

?

二、Java內存模型(JMM)

線程與內存交互操作如下?

所有的變量(實例字段,靜態字段,構成數組對象的 元素,不包括局部變量和方法參數)都存儲在主內存中,每個線程有自己的工作內存,線程的工作內存保存被線程使用到變量的主內存副本拷貝線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存的變量。不同線程之間也不能直接訪問對方工作內存中的變量,線程間變量值的傳遞通過主內存來完成。
?

1、Java內存模型定義了八種操作

lock(鎖定):作用于主內存的變量,它把一個變量標識為一個線程獨占的狀態;
unlock(解鎖):作用于主內存的變量,它把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定;
read(讀取):作用于主內存的變量,它把一個變量的值從主內存傳送到線程中的工作內存,以便隨后的load動作使用;
load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中;
use(使用):作用于工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎;
assign(賦值):作用于工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存中的變量;
store(存儲):作用于工作內存的變量,它把工作內存中的一個變量的值傳送到主內存中,以便隨后的write操作;
write(寫入):作用于主內存的變量,它把store操作從工作內存中得到的變量的值寫入主內存的變量中。

2、volatile關鍵字作用:

1)、當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量值立即刷新到主內存中。
2)、當讀一個volatile變量時,JMM會把該線程對應的本地內存設置為無效,直接從主內存中讀取共享變量。
3)、禁止指令重排序優化
4)、volatile關鍵字不能保證在多線程環境下對共享數據的操作的正確性。可以使用在自己狀態改變之后需要立即通知所有線程的情況下,也就是說volatile不能保證線程同步,Java語言提供了一種稍弱的同步機制,即volatile變量,用來確保將變量的更新操作通知到其他線程,這就是所謂的線程可見性,當把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值,所以volatile具有可見性。對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入,然后synchronized也是具有可見性。

5)、volatile的原子性:對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復合操作不具有原子性,所以volatile能保證可見性,不能保證原子性。

?

理解volatile特性的一個好方法是:把對volatile變量的單個讀/寫,看成是使用同一個監視器鎖對這些單個讀/寫操作做了同步

class VolatileFeaturesExample {volatile long vl = 0L;  //使用volatile聲明64位的long型變量public void set(long l) {vl = l;   //單個volatile變量的寫}public void getAndIncrement () {vl++;    //復合(多個)volatile變量的讀/寫}public long get() {return vl;   //單個volatile變量的讀}
}

假設有多個線程分別調用上面程序的三個方法,這個程序在語意上和下面程序等價:

class VolatileFeaturesExample {long vl = 0L;               // 64位的long型普通變量public synchronized void set(long l) {     //對單個的普通 變量的寫用同一個監視器同步vl = l;}public void getAndIncrement () { //普通方法調用long temp = get();           //調用已同步的讀方法temp += 1L;                  //普通寫操作set(temp);                   //調用已同步的寫方法}public synchronized long get() { //對單個的普通變量的讀用同一個監視器同步return vl;}
}

3 volatile和synchronized區別?
1)、volatile僅能使用在變量級別;synchronized則可以使用在變量、方法和類級別;

2)、volatile讀數據直接從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。

3)、volatile能保證可見性,不能保證原子性;而synchronized則可以保證可見性和原子性;

4)、volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。

5)、volatile標記的變量不會被編譯器優化進行指令重排列;synchronized標記的變量可以被編譯器優化進行指令重排列。

?

?

三、JMM特性

1)、原子性

就是指該操作是不可再分的。不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進行操作。簡而言之,在整個操作過程中不會被線程調度器中斷的操作,都可認為是原子性。比如 a = 1;

?

2)、可見性

可見性是指當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改

?

3)、?有序性

Java內存模型中有序性可歸納為這樣一句話:如果在本線程內觀察,所有操作都是有序的,如果在一個線程中觀察另一個線程,所有操作都是無序的

是指對于單線程的執行代碼,執行是按順序依次進行的。但在多線程環境中,則可能出現亂序現象,因為在編譯過程會出現“指令重排”,重排后的指令與原指令的順序未必一致

?


四、java中的線程安全等級

不可變
可以是基本類型的final;可以是final對象,但對象的行為不會對其狀態產生任何影響,比如String的subString就是new一個String對象各種Number類型如BigInteger和BigDecimal等大數據類型都是不可變的,但是同為Number子類型的AtomicInteger和AtomicLong則并非不可變。原因與它里面狀態對象是unsafe對象有關,所做的操作都是CAS操作,可以保證原子性。

絕對線程安全
不管運行時環境如何,調用者都不需要任何額外的同步措施。

相對線程安全
這是我們通常意義上的線程安全。需要保證對象單獨的操作是線程安全的。比如Vector,HashTable,synchronizedCollection包裝集合等。

線程兼容
對象本身不是線程安全的,但可以通過同步手段實現。一般我們說的不是線程安全的,絕大多數是指這個。比如ArrayList,HashMap等。

線程對立
不管調用端是否采用了同步的措施,都無法在并發中使用的代碼。

?

?

?

五、線程安全的實現方式

要實現線程安全一般至少需要兩個特性:原子性和可見性

1)使用synchronize:它本具有原子性和可見性的,所以如果使用了synchronize修飾的操作,那么就自帶了可見性,synchronized使用悲觀鎖來實現線程安全

2)使用原子類代替基本數據類型,原子類是使用樂觀鎖來實現線程安全,多線程環境下執行a++,可以使用AtomicInteger類incrementAndGet()方法實現,同樣是使用了volatile來保證可見性;使用Unsafe調用native本地方法CAS,CAS采用總線加鎖或緩存加鎖方式來保證原子性。

3 )?使用volatile關鍵字,volatile不一定就有原子性,比如用volatile修飾的變量進行++或者--操作(num++),我們需要讓volatile修飾的變量需要具有原子性,那么我們一般可以設置在boolean類型變量上,如下

volatile boolean tag = true;
線程1 while(tag){};
線程2 while(tag){};

如果有變量自增或者自減,我們可以使用原子類(AtomicInteger)

4)使用ThreadLocal對各個線程進行隔離

可以參考我的這篇博客 :Java之ThreaLocal

5)我們還可以用其他的鎖,比如重入鎖(ReentrantLock) 保證線程安全

6)我們還可以用 臨界區互斥量、信號量 保證線程安全

?

參考文獻:

https://developer.51cto.com/art/201910/605093.htm

https://www.open-open.com/lib/view/open1459412319988.html

https://www.iteye.com/blog/smallbug-vip-2275743

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

https://blog.csdn.net/jingzi123456789/article/details/78004074

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287873.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287873.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287873.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

animate 實現滑動切換效果

今天和大家分享一下用 animate 實現滑動切換效果的小例子 ------- 來自<一只有夢想的前端小白> 大家都知道jQuery 提供的有一下幾種方法能夠實現滑動效果&#xff1a; slideDown()slideUp()slideToggle()但是以上的滑動不太方便控制其滑動的方向&#xff0c;所以我們還是…

[不一樣的依賴注入]通過遞歸實現容器里依賴注入

遞歸實現依賴注入創建所需的依賴服務類1public class Test2{3 public void PrintTest()4 {5 Console.WriteLine("Hello World");6 }7}89public class Test2 10{ 11 private readonly Test _test; 12 13 public Test2(Test test) 14 { 15 …

ArcGIS實驗教程——實驗十七:緩沖區分析(Buffer Analysis)

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據) 【實驗描述】 緩沖區(Buffer)是為了識別某一地理實體對周圍地物的影響而在其周圍建立的一定寬度多邊形區域,緩沖區分析(Buffer Analysis)是用來確定不同地理要素的空間臨近性或接近程度的一種分…

Java之jdk和CGLib實現動態代理

1 jdk實現動態代理源碼實現 這里需要用到InvocationHandler接口 public interface Hello {public void sayHello(); } public class HelloImpl implements Hello {Overridepublic void sayHello() {System.out.println("hello word");} }import java.lang.reflect…

從Visual Studio中生成Linux設備

本文講的是從Visual Studio中生成Linux設備&#xff0c;【IT168 云計算頻道】近日Novell發布了SUSE Studio&#xff1a;一個用于創建Linux設備&#xff08;appliance&#xff09;的工具。與此同時&#xff0c;Mono小組創建了一個插件以從Visual Studio中生成支持SUSE的設備。 …

C++入門指南及實戰 第二步 HelloWorld及擴展詳解

回顧 在上一節中&#xff0c;我們編寫了如下代碼&#xff0c;完成了 HelloWorld程序的編寫&#xff1a; #include<iostream> using namespace std;int main(){cout <<"Hello World";return 0; }本小節將會對該代碼進行講解&#xff0c;并且解釋一下專業…

2560x1600分辨率高嗎_做設計還弄不清分辨率和像素之間的關系,來了解下他們是怎么換算...

許多同學都在問我關于像素的問題&#xff0c;為什么印刷時要300分辨率以上&#xff1f;網頁為什么72就夠了&#xff1f;做戶外噴繪30&#xff0c;甚至巨幅畫面20就上了。關于這些還是很多人不知道的&#xff0c;要不也不會被賣手機的忽悠&#xff0c;各大手機推銷員拿著手機大聲…

使用 fixture 機制重構 appium_helloworld

一、前置說明 在 pytest 基礎講解 章節,介紹了 pytest 的特性和基本用法,現在我們可以使用 pytest 的一些機制,來重構 appium_helloworld 。 appium_helloworld 鏈接: 編寫第一個APP自動化腳本 appium_helloworld ,將腳本跑起來 代碼目錄結構: pytest.ini 設置: [pyt…

linux程序調試命令strace

strace命令用法詳解: strace常用來跟蹤進程執行時的系統調用和所接收的信號。 在Linux世界&#xff0c;進程不能直接訪問硬件設備&#xff0c;當進程需要訪問硬件設備(比如讀取磁盤文件&#xff0c;接收網絡數據等等)時&#xff0c;必須由用戶態模式切換至內核態模式&#xff0…

Tomcat相關 -- 內存設置

java內存溢出詳解 一、常見的java內存溢出 1、java.lang.OutOfMemmoryError : Java heap space -- JVM Heap &#xff08;jvm 堆溢出&#xff09; JVM啟動時自動設置JVM Heap的值&#xff0c;其初始空間(即 -Xms)是物理內存的1/64&#xff0c;最大空間(-Xms)不可超過物理內存。…

CoreWCF 1.0 正式發布,支持 .NET Core 和 .NET 5+ 的 WCF

CoreWCF 項目組正式發布 1.0 版本的 CoreWCF, 這是面向 .NET Core 平臺的 WCF 移植版本。它支持 SOAP、NetTCP 和 WSDL 的相同實現。在代碼中的使用方式于 WCF 相同&#xff0c;但是升級到使用 ASP.NET Core 作為服務宿主&#xff0c;并工作在 .NET Core 平臺上。這是該項目的第…

【經典回放】多種語言系列數據結構算法:快速排序

快速排序(Quicksort)是對冒泡排序的一種改進。 快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過…

表頭合并_多個Excel表格合并數據麻煩?試試Power Query輕松幫你解決

Hi!大家好&#xff01;歡迎來到小龍自修室&#xff01;又到了小龍分享時間&#xff01;(今天的內容有點多&#xff0c;希望各位看官一定要看到最后&#xff01;有驚喜)上一篇文字小龍和大家一起制作了一個限定數據內容錄入的電子表格&#xff0c;我的表格我做主&#xff01;表格…

C++入門指南及實戰 第三步 基本變量

在C編程中&#xff0c;內置了一些基本數據類型用來存儲一些不同類型的值。有字符類型 char 用以存儲字符&#xff0c;如a、b、c、d、-、、1、2、4、3、>、?等&#xff1b;有整形 int 用以存儲整數類型&#xff0c;如1、2、3、4、5、11、111、2311等&#xff1b;有浮點類型 …

OPCServer Modbus使用和配置

一&#xff0c;安裝KEPware.Enhanced.OPC.DDE.KEPServer。(PLC數據傳送給KEPServer,開發的程序用OPCServer讀KEPServer) 設置ip地址后面是指的plc站號&#xff0c;此處必須和plc上站號對應。否則無法接收數據。 打開quick client 查看傳值情況 二.C#程序代碼 引用opcdaauto.dll…

C# 創建命名管道服務器

通過創建 NamedPipeServerStream 的一個新實例&#xff0c;來創建服務器。NamedPipeServerStream 派生自基類 PipeStream&#xff0c;PipeStream 派生自 Stream 基類&#xff0c;因此可以使用流的所有功能&#xff0c;例如&#xff0c;可以創建CryptoStream 或 GZipStream&…

在香蕉派 Banana Pi BPI-M1上使用 開源 OxOffice Impress

2019獨角獸企業重金招聘Python工程師標準>>> 在香蕉派 Banana Pi BPI-M1上使用 開源 OxOffice Impress 在Banana Pi BPI-M1上使用OxOffice Impress&#xff0c;該簡報大小約26MB&#xff0c;採用自動播放機制。 OxOffice提供 arm linux的版本&#xff0c;可佈署在ar…

免殺新姿勢:利用線程將惡意代碼注入到內存中

本文講的是免殺新姿勢&#xff1a;利用線程將惡意代碼注入到內存中&#xff0c;產生存放遠程攻擊線程的進程在這篇文章中我不想一步一步解釋我編寫的C#代碼&#xff0c;但是我會展示下它能夠繞過殺毒軟件&#xff0c;并且操作非常簡單&#xff0c;而且實用。 首先說明一下&…

【經典回放】多種語言系列數據結構算法:希爾排序

【希爾排序原理】每隔sp(整數)個數即取數并判斷大小,交換,先構造局部有序序列,直到sp為1,構造完整的有序序列。 給出一組數據,如下: 0 1 2 3

Java之解決散列表的沖突用開放定址法和鏈表法

1 問題 理想狀態下&#xff0c;散列表就是一個包含關鍵字的固定大小的數組&#xff0c;通過使用散列函數&#xff0c;將關鍵字映射到數組的不同位置&#xff0c;哈希函數可以將關鍵字均勻的分散到數組的不同位置&#xff0c;不會出現兩個關鍵字散列值相同&#xff08;假設關鍵…