0x15Java引用賦值,是原子操作嗎? 線程安全嗎?

Q1什麼是原子操作

所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位,因此這裏的原子實際是使用了物理學裏的物質微粒的概念。

Q2非原子的64位操作

這是一個局部的概念,大多地方我們遇不到這樣的說法

當線程在沒有同步的情況下讀取變量時,可能會得到一個失效值,但至少這個值是由之前某個線程設置的值,而不是一個隨機值。這種安全性保證也被稱爲最低安全性( out-of-thin-air safety)。

最低安全性適用於絕大多數變量,但是存在一個例外:非volatile 類型的64位數值變量(double和long,請參見3.1.4節)。Java內存模型要求,變量的讀取操作和寫入操作都必須是原子操作,但對於非volatile類型的long和double變量,JVM允許將64位的讀操作或寫操作分解爲兩個32位的操作。當讀取一個非volatile類型的long變量時,如果對該變量的讀操作和寫操作在不同的線程中執行,那麼很可能會讀取到某個值的高32位和另一個值的低32位。因此,即使不考慮失效數據問題,在多線程程序中使用共享且可變的long和double等類型的變量也是不安全的,除非用關鍵字volatile來聲明它們,或者用鎖保護起來。

Q3 Java中 有哪些數據類型,它們分別佔用的空間大小是多少

一、基本數據類型:

byte:Java中最小的數據類型,在內存中佔8位(bit),即1個字節,取值範圍-128~127,默認值0

short:短整型,在內存中佔16位,即2個字節,取值範圍-32768~32717,默認值0

int:整型,用於存儲整數,在內在中佔32位,即4個字節,取值範圍-2147483648~2147483647,默認值0

long:長整型,在內存中佔64位,即8個字節-263~263-1,默認值0L

float:浮點型,在內存中佔32位,即4個字節,用於存儲帶小數點的數字(與double的區別在於float類型有效小數點只有6~7位),默認值0

double:雙精度浮點型,用於存儲帶有小數點的數字,在內存中佔64位,即8個字節,默認值0

char:字符型,用於存儲單個字符,佔16位,即2個字節,取值範圍0~65535,默認值爲空

boolean:布爾類型,佔1個字節,用於判斷真或假(僅有兩個值,即true、false),默認值false

二、引用數據類型:

類、接口類型、數組類型、枚舉類型、註解類型。

區別:

基本數據類型在被創建時,在棧上給其劃分一塊內存,將數值直接存儲在棧上。

引用數據類型在被創建時,首先要在棧上給其引用(句柄)分配一塊內存,而對象的具體信息都存儲在堆內存上,然後由棧上面的引用指向堆中對象的地址。

例如,有一個類Person,有屬性name,age,帶有參的構造方法,

Person p = new Person("zhangsan",20);

在內存中的具體創建過程是:

1.首先在棧內存中位其p分配一塊空間;

2.在堆內存中爲Person對象分配一塊空間,併爲其三個屬性設初值"",0;

3.根據類Person中對屬性的定義,爲該對象的兩個屬性進行賦值操作;

4.調用構造方法,爲兩個屬性賦值爲"Tom",20;(注意這個時候p與Person對象之間還沒有建立聯繫);

5.將Person對象在堆內存中的地址,賦值給棧中的p;通過引用(句柄)p可以找到堆中對象的具體信息。

相關知識:

靜態區: 保存自動全局變量和 static 變量(包括 static 全局和局部變量)。靜態區的內容在總個程序的生命週期內都存在,由編譯器在編譯的時候分配。

堆區: 一般由程序員分配釋放,由 malloc 系列函數或 new 操作符分配的內存,其生命週期由 free 或 delete 決定。在沒有釋放之前一直存在,直到程序結束,由OS釋放。其特點是使用靈活,空間比較大,但容易出錯

棧區: 由編譯器自動分配釋放,保存局部變量,棧上的內容只在函數的範圍內存在,當函數運行結束,這些內容也會自動被銷燬,其特點是效率高,但空間大小有限

文字常量區: 常量字符串就是放在這裏的。 程序結束後由系統釋放。

Q4有哪些操作是原子操作

有一些操作比如 int 變量的賦值,引用對象的賦值,

這些的開銷很小,甚至我們似乎可以把他們理解爲原子性的操作。它們在某些平臺是原子性的。

但最後的結論應是:

除非代碼所工作的操作系統平臺環境或者java官方指定這個操作是原子性操作,線程安全的。我們不應該把它當做原子性的操作,線程安全性的操作。

那麼引用進行賦值不是線程安全的,不是原子性的。至少java沒有這樣答應我們,因爲它提供了原子操作類

JDK1.5之後的java.util.concurrent.atomic包裏,多了一批原子處理類。

  • 標量類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 數組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 複合變量類:AtomicMarkableReference,AtomicStampedReference

AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference這四種基本類型用來處理布爾,整數,長整數,對象四種數據,其內部實現不是簡單的使用synchronized,而是一個更爲高效的方式CAS (compare and swap) + volatile和native方法,從而避免了synchronized的高開銷,執行效率大爲提升。

結論

其實發現是自己跟自己挖了一個坑,答案很簡單。

除非代碼所工作的操作系統平臺環境或者java官方指定這個操作是原子性操作,線程安全的。我們不應該把它當做原子性的操作,線程安全性的操作。

基於CAS的線程安全機制很好很高效,但要說的是,並非所有線程安全都可以用這樣的方法來實現,這隻適合一些粒度比較小,型如計數器這樣的需求用起來纔有效

歡迎訪問我的小站:學而

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章