原子性
原子性的定義:
所謂的原子性是指在一次操作或者多次操作中,要麼所有的操作全部都得到了執行並且不會受到任何因素的干擾而中斷,要麼所有的操作都不執行,多個操作是一個不可以分割的整體。簡單點就是:要麼這個操作不進行,要麼就進行到底,就不用擔心執行緒切換問題
count++不是一個原子性操作,他在執行的過程當中有可能被其他執行緒打斷。
count++實際上在底層是:
tamp=count+1;
count=tamp;
然後再:
- 從共享資料中讀取資料到本執行緒棧中
- 修改本執行緒棧中變數副本的值,
- 把本執行緒中變數副本的值賦值給共享資料
這其中的每一步都有可能在執行的過程中被別的執行緒打斷,多以count++不是一個
原子性操作
volatile關鍵字也不能保證原子性:
- volatile關鍵字只能保證執行緒每次讀取的值都是最新的值(都是共享資料本身的值)
- 但是不能保證原子性
加鎖可以保證操作的原子性
原子類 AtomicInteger
概述:java從JDK1.5開始提供了java.util.concurrent.atomic包(簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單,效能高效,執行緒安全地更新一個變數的方式。因為變量的型別有很多種,所以在Atomic包裡一共提供了13個類,屬於4種類型的原子更新方式,分別是原子更新基本型別、原子更新陣列、原子更新引用和原子更新屬性(欄位)
使用原子的方式更新基本型別,使用原子的方式更新基本型別Atomic包提供了以下3個類:
- AtomicBoolean: 原子更新布林型別
- AtomicInteger: 原子更新整型
- AtomicLong:
以上3個類提供的方法幾乎一模一樣,所以本節僅以AtomicInteger為例進行講解,AtomicInteger的常用方法如下:
- public AtomicInteger(): 初始化一個預設值為0的原子型Integer
- public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer
- int get(): 獲取值
- int getAndIncrement(): 以原子方式將當前值加1,注意,這裡返回的是自增前的值。
- int incrementAndGet(): 以原子方式將當前值加1,注意,這裡返回的是自增後的值。
- int addAndGet(int data): 以原子方式將輸入的數值與例項中的值(AtomicInteger裡的value)相加,並返回結果。
- int getAndSet(int value): 以原子方式設定為newValue的值,並返回舊值。
程式碼實現:
1 package com.itheima.threadatom3; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 public class MyAtomIntergerDemo1 { 6 // public AtomicInteger(): 初始化一個預設值為0的原子型Integer 7 // public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer 8 public static void main(String[] args) { 9 AtomicInteger ac = new AtomicInteger(); 10 System.out.println(ac); 11 12 AtomicInteger ac2 = new AtomicInteger(10); 13 System.out.println(ac2); 14 } 15 16 }
1 package com.itheima.threadatom3; 2 3 import java.lang.reflect.Field; 4 import java.util.concurrent.atomic.AtomicInteger; 5 6 public class MyAtomIntergerDemo2 { 7 // int get(): 獲取值 8 // int getAndIncrement(): 以原子方式將當前值加1,注意,這裡返回的是自增前的值。 9 // int incrementAndGet(): 以原子方式將當前值加1,注意,這裡返回的是自增後的值。 10 // int addAndGet(int data): 以原子方式將引數與物件中的值相加,並返回結果。 11 // int getAndSet(int value): 以原子方式設定為newValue的值,並返回舊值。 12 public static void main(String[] args) { 13 // AtomicInteger ac1 = new AtomicInteger(10); 14 // System.out.println(ac1.get()); 15 16 // AtomicInteger ac2 = new AtomicInteger(10); 17 // int andIncrement = ac2.getAndIncrement(); 18 // System.out.println(andIncrement); 19 // System.out.println(ac2.get()); 20 21 // AtomicInteger ac3 = new AtomicInteger(10); 22 // int i = ac3.incrementAndGet(); 23 // System.out.println(i);//自增後的值 24 // System.out.println(ac3.get()); 25 26 // AtomicInteger ac4 = new AtomicInteger(10); 27 // int i = ac4.addAndGet(20); 28 // System.out.println(i); 29 // System.out.println(ac4.get()); 30 31 AtomicInteger ac5 = new AtomicInteger(100); 32 int andSet = ac5.getAndSet(20); 33 System.out.println(andSet); 34 System.out.println(ac5.get()); 35 } 36 }
Atomic的記憶體解析:
Atomic原理:自旋鎖+CAS演算法
CAS演算法:
先給一段比較晦澀的概念:
- 有3個運算元(記憶體值V, 舊的預期值A,要修改的值B)
- 當舊的預期值A == 記憶體值 此時修改成功,將V改為B
- 當舊的預期值A!=記憶體值 此時修改失敗,不做任何操作
- 並重新獲取現在的最新值(這個重新獲取的動作就是自旋)
我們圖解解釋什麼是CAS演算法和自旋:
現在假設有AB兩個執行緒使用記憶體中的共性資料100,如圖所示:
當AB完成初始化取值時:
此時A執行加一操作(Atomic),則原來的副本100就會變成舊值,新值(要修改的值)是101,101要賦值給副本,這個時候比較一下舊值和記憶體中共享資料值是否相等。
如果相等則把新值賦值給記憶體中共享資料,如果不等(不等就表明被別的執行緒操作過這裡假設B執行緒操作過)則把記憶體中的值重新賦值給賦值給副本,自增,原值變舊值,然後舊值再與記憶體中的共享資料比較相等則把新值賦值給共享,不等重複以上操作。。。如下圖:
重複比較這一步稱作為自旋