一、復習
公平鎖,非公平鎖,可重入鎖,自旋鎖,獨占鎖和共享鎖
二、Java并發包中的ThreadLocalRandom類
1.起源以及優點
ThreadLocalRandom類是在JDK7的JUC包開始新增的類,彌補了Random類在高并發環境下的缺點
2.Random類以及局限性
java.util.Random類是一種常用的亂數生成器,在java.land.Math中的亂數也是使用的Random的實體,下面先舉個例子
package com.ruigege.PricipleAnalyzingOfThreadLocalRandom3;
import java.util.Random;
public class RandomTest {
public static void main(String[] args) {
Random random = new Random();
for(int i=0;i<5;i++) {
System.out.println(random.nextInt(5));//輸出五個5以內的亂數
}
}
}

決議:創建一個使用默認種子的亂數生成器,然后傳入一個限制5,讓其生成一個5以內的隨機整數, 首先種子是什么意思?這就要講到Random的原理,基本原理就是,我們可以通過函式傳入一個數字,或者使用無參建構式創建實體(然而實際上代碼內部會根據某個演算法生成一個默認的數字),然后根據傳入的引數或者內部自己生成的數字,作為起點,根據某些隨機演算法生成下一個數字,然后依次類推,下一個亂數的生成是依賴于上一個亂數的, 我們看一下nextInt的原始碼
public int nextInt(int bound){
//引數檢查
if(bound<0){
throw new IllegalArgumentException(BadBound);
}
int r=next(31);
//根據新的種子來計算下一個種子
.......
return r;
}
決議:int r=next(31)相當于seed=f(seed)函式,利用31來生成一個亂數r,然后回傳r;下面的步驟省略了,其實可以抽象為g(seed,bound),保證生成的數字能夠在bound的范圍內, 由于這種機制,每一個亂數生成是依賴上一個亂數的,因此在多執行緒的情況下,多個執行緒生成的亂數都是相同的,這并不是我們希望得到的,下面看一下next的代碼
protected int next(int bits){
long oldseed,nextseed;
AtomicLong seed = this.seed;
do{
oldseed = seed.get();//(1)
nextseed = (oldseed * multiplier + addend) & mask;//(2)
}while(!seed.compareAndSet(oldseed,nextseed));//(3)
return (int)(nextseed >>> (48-bits));//(4)
(1)代表獲取當前原子變數種子的值;(2)代表了一種演算法,這種演算法就是根據前一個種子變數來產生下一個種子變數;(3)其實是我們之前講過的CAS操作,保證在多執行緒的情況下依然能夠保證一致性,這個執行陳述句就是指一個執行緒判斷老種子是否和自己已知的種子是否一樣,如果一樣的話,那么就設定舊種子為新種子值,如果不一致就會進入到回圈之中,直至判斷為true,接著繼續下面的操作,這個陳述句是為了保證操作的一致性,;(4)拿到新種子,并且根據之前給的數值限制,來回傳一個亂數,
3.總結
使用Random類的實體,基本原理就是定義一個原子性long變數,然后根據舊種子生成新的種子,最后回傳亂數,由于在多執行緒情況下,只有一個執行緒才能實作原子性變數的一系列操作,因為其他執行緒就是自旋,造成執行緒并發性大大降低,另外,多個執行緒獲取得亂數都是一樣,那么這樣真的"隨機”嗎? 針對這種情況,出現了并發下的亂數類ThreadLocalRandom
三、ThreadLocalRandom類
這個類實作原理有些像ThradLocals變數,ThreadLocalRandom其實也是一個工具類,具體的亂數變數是Thread類的內置成員變數, 首先創建一個測驗類
package com.ruigege.PricipleAnalyzingOfThreadLocalRandom3;
import java.util.concurrent.ThreadLocalRandom;
public class ThreadLocalRandomTest {
public void main(String[] args) {
ThreadLocalRandom random = ThreadLocalRandom.current();
for(int i=0;i<10;i++) {
System.out.println(random.nextInt(5));
}
}
}

使用該實體輸出10個5以內的亂數
1.基本原理
與ThreadLocal類似,在Thread內部 維護一個亂數的變數,然后通過該工具類去在每個執行緒中,保留一個副本(其實就是一個執行緒一套玩法),避免了對共享變數進行同步,Random的缺點就是多個執行緒會對同一個原子性種子變數,從而導致對原子變數的更新競爭, 每個執行緒自己內部維護一個種子變數的副本,這樣多個執行緒執行緒就不對競爭原始的共享變數了,大大增強了并發性,
2.原始碼分析
直接上UML圖來進行解釋 
(1)繼承關系:ThreadLocalRandom繼承了Random類 (2)前三個成員變數,我們先不用關注,用到了再說 (3)instance變數是一個TheadLocalRandom實體,它是通過current()這個靜態方法來創建一個實體,然后回傳,也就是說,我們測驗的時候,用內置方法即可獲取實體,而不需要使用構造方法,來獲取實體, (4)probeGenerator和seeder兩個原子性變數,是我們初始化呼叫執行緒的種子和探針變數的時候會用到它們,每個執行緒之后呼叫一次, (5)nextInt方法,傳參限制數,然后生成下一個亂數, (6)nextSeed()方法根據上一個種子數值來生成下一個種子數值 (7)對于Thread類,里面有一個非原子性變數threadLocalRandomSeed,這個就是一個執行緒自己一個種子變數,不和其他執行緒公用,這樣就能避免上面提到的共享變數沖突,有些類似于threadLocal變數,通過這個原理ThreadLocalRandom其實就一個工具類,內含一些和執行緒無關的通用演算法,具體變數會放到每個執行緒的實體中,所以它是執行緒安全的,
3.初始化變數的部分
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
//使用Unsafe機制,獲取一個實體,并使用它來獲取成員變數的偏移量
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> thread = Thread.class;
//獲取thread內部成員變數的偏移量
SEED = UNSAFE.objectFieldOffset(thread.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset(thread.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset(thread.getDeclaredField("threadLocalRandomSecondary"));
}catch(Exception e) {
e.printStackTrace();
}
}
基本看注釋即可
4.看一下current函式
static final ThreadLocalRandom instance = new ThreadLcoalRandom();
public static ThreadLocalRandom current() {
//這里做一個判斷,也就是當前執行緒中的PROBE這個成員變數的值為0嗎?
//如果為0,說面這是第一次呼叫,我們先要進行初始化該實體,再回傳,
if(UNSAFE.getInt(Thread.currentThread(),PROBE)==0) {
localInit();
}
return instance;
}
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p==0)?1:p;//跳過0
int seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
UNSAFE.putLong(t, SEED,seed);
UNSAFE.putInt(t, PROBE,probe);
}
使用方法current獲取一個static型別的實體,各個執行緒是共用一個工具類實體,基本進入就是初始化,講各個變數賦值一下,這樣的設定,只用呼叫的時候才會初始化與亂數有關的變數,這樣能夠提高效率,優化程式,
5.nextInt(int bound)方法
public int nextInt(int bound) {
//引數校驗
if(bound<0) {
throw new IllegalArgumentException(BadBound);
}
//根據當前執行緒中的種子來計算下一個種子
int r = mix32(nextSeed());
//下面就一個根據bound來回傳一個亂數的演算法了
int m = bound-1;
if((bound & m)==0) {
r&= m;
}else {
for (int u=r>>>1;u+m-(r=u%bound)<0;u = mix32(nextSeed())>>>1);
}
return r;
}
final long nextSeed() {
Thread t = Thread.currentThread();
long r = UNSAFE.getLong(t,SEED)+GAMMA;
//將r的值,放到當前執行緒中SEED變數中
UNSAFE.putLong(t, SEED,r);
return r;
}
四、原始碼:
所在包:com.ruigege.OtherFoundationOfConcurrent2 https://github.com/ruigege66/ConcurrentJavaCSDN:https://blog.csdn.net/weixin_44630050 博客園:https://www.cnblogs.com/ruigege0000/ 歡迎關注微信公眾號:傅里葉變換,個人賬號,僅用于技術交流 
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/234132.html
標籤:其他
上一篇:Java 單元測驗撰寫完全教程(TestNG + Mockito + Powermock)
下一篇:二、基本資料型別
