volatile
volatile是Java虛擬機提供的輕量級的同步機制
volatile提供的輕量級的同步機制
1.1保證可見性 1.2不保證原子性 1.3禁止指令重排
1.4JMM(Java Memory Model)
Java內存模型,簡稱JMM ,其本身是一種抽象的概念並不是真實存在,它描述的是一組規則或規范,通過這組規范定義了程序中的各個變量(包括實例字段,靜態字段和構成數組的元素)的訪問方式。
JMM關於同步的規定:
1.線程加鎖前,必須把共享的值刷新回主內存
2.線程加鎖前,必須讀取內存的最新值到自己的工作空間
3.加瑣解鎖是同一把鎖
由於JVM運行程序的實體是線程,而每個線程創建時jvm都會為其創建一個工作內存(有些地方稱為棧空間)工作內存是每個線程的私有數據區域,
而java內存模型中規定所有的變量都存儲在主內存,主內存是共享內存區域,所有的線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內存中進行,
首先要將變量拷貝到自己的工作內存空間,然后對變量進行操作,操作完成后將變量寫回主內存,不能直接操作內存中的變量,
各個線程的工作內存中存儲着主內存中的變量副本拷貝,因此不同的線程無法訪問對方的工作內存,線程間的通信(傳值)必須通過主內存來完成。
1.4.1可見性
通過前面對JMM的介紹,我們知道各個線程對主內存中共享變量的操作都是各個線程各自拷貝到自己的工作內存操作后再寫回主內存中的.這就可能存在一個線程AAA修改了共享變量X的值還未寫回主內存中時 ,另外一個線程BBB又對內存中的一個共享變量X進行操作,
但此時A線程工作內存中的共享比那里X對線程B來說並不不可見.這種工作內存與主內存同步延遲現象就造成了可見性問題.
1.4.2原子性
number++在多線程下是非線程安全的,如何不加synchronized解決?
VolatileDemo代碼演示可見性+原子性代碼
有序性
計算機在執行程序時,為了提高性能,編譯器和處理器常常會做指令重排,一把分為以下3中單線程環境里面確保程序最終執行結果和代碼順序執行的結果一致.處理器在進行重新排序是必須要考慮指令之間的數據依賴性多線程環境中線程交替執行,
由於編譯器優化重排的存在,兩個線程使用的變量能否保持一致性是無法確定的,結果無法預測
內存屏障(Memory Barrier)
利用該特征實現volatile的內存可見性
由於編譯器和處理器都能執行指令重排優化。如果在指令間插入一條Memory Barrier 則會告訴編譯器和CPU,不管什么指令都不能和這條Memory Barrier指令重排序,也就是說通過插入內存屏障禁止在內存屏障前后的指令執行重排序優化。
內存屏障另外一個作用是強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據上的最新版本
重排1
public void mySort(){ int x=11;//語句1 int y=12;//語句2 x=x+5;//語句3 y=x*x;//語句4 } 123421341324 問題:請問語句4 可以重排后變成第一條碼?存在數據的依賴性 沒辦法排到第一個
重排2
int a ,b ,x,y=0;線程1線程2x=a;y=b;b=1;a=2;x=0 y=0 如果編譯器對這段代碼進行執行重排優化后,可能出現下列情況:線程1線程2b=1;a=2;x=a;y=b;x=2 y=1 這也就說明在多線程環境下,由於編譯器優化重排的存在,兩個線程使用的變量能否保持一致是無法確定的.
禁止指令重排小總結(了解)
工作內存與內存同步延遲現象導致的可見性問題
可以使用synchronized或volatile關鍵字解決,他們都可以使一個線程修改后的變量立即對其他的線程可見
對指令重排序導致的可見性問題和有序性問題
可以利用volatile關鍵字解決,因為volatile的另一個作用就是禁止重排序優化
DCL(雙端檢測機制)
DCL(雙端檢測機制)不一定線程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排,原因在某一線程執行到第一次檢測,讀取到的instance不為null時,instance的引用對象可能沒有完成初始化。 instance = new SingletonDemo();可分為以下3步完成(偽代碼)
//1.分配對象內存空間
memory = allocate();
//2.初始化對象
instance(memory);
//3.設置instance指向剛分配的內存地址,此時 instance != null
instance = memory;
步驟2和3不存在數據依賴關系,而且無論重排前還是重排后的程序的執行結果在單線程中並沒有改變,因此這種重排優化是允許的
//1.分配對象內存空間
memory = allocate();
//3.設置instance指向剛分配的內存地址,此時 instance != null,但是對象還沒有初始化完成!
instance = memory;
//2.初始化對象
instance(memory);
但是指令重排只會保證串行語義的執行一致性(單線程),但並不會關心多線程間的語義一致性。
所以當一條線程訪問instance不為null時,由於instance實例並未初始化完成,也就造成線程安全問題。
你在哪些地方用到過volatile?
3.1 單例模式DCL代碼

public class SingletonDemo { private static volatile SingletonDemo instance=null; private SingletonDemo(){ System.out.println(Thread.currentThread().getName()+"\t 構造方法"); } /** * 雙重檢測機制 * @return */ public static SingletonDemo getInstance(){ if(instance==null){ synchronized (SingletonDemo.class){ if(instance==null){ instance=new SingletonDemo(); } } } return instance; } public static void main(String[] args) { for (int i = 1; i <=10; i++) { new Thread(() ->{ SingletonDemo.getInstance(); },String.valueOf(i)).start(); } }}
3.2代理模式volatile分析

DCL(雙端檢鎖) 機制不一定線程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排 原因在於某一個線程在執行到第一次檢測,讀取到的instance不為null時,instance的引用對象可能沒有完成初始化.instance=new SingletonDem(); 可以分為以下步驟(偽代碼) memory=allocate();//1.分配對象內存空間instance(memory);//2.初始化對象instance=memory;//3.設置instance的指向剛分配的內存地址,此時instance!=null 步驟2和步驟3不存在數據依賴關系.而且無論重排前還是重排后程序執行的結果在單線程中並沒有改變,因此這種重排優化是允許的.memory=allocate();//1.分配對象內存空間instance=memory;//3.設置instance的指向剛分配的內存地址,此時instance!=null 但對象還沒有初始化完.instance(memory);//2.初始化對象但是指令重排只會保證串行語義的執行一致性(單線程) 並不會關心多線程間的語義一致性所以當一條線程訪問instance不為null時,由於instance實例未必完成初始化,也就造成了線程安全問題.
CAS比較並交換
1.比較並交換
/** * Description * * @author veliger@163.com * @version 1.0 * @date 2019-04-12 9:57 * 1.什么是CAS ? ===> compareAndSet * 比較並交換 **/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2014)+"\t current"+atomicInteger.get());
}}
2.CAS底層原理?如果知道,談談你對UnSafe的理解
atomicInteger.getAndIncrement();
atomicInteger.getAndIncrement()方法的源代碼: /** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } 印出來一個問題:UnSafe類是什么?
UnSafe
1.UnSafe 是CAS的核心類 由於Java 方法無法直接訪問底層 ,需要通過本地(native)方法來訪問,UnSafe相當於一個后面,基於該類可以直接操作特額定的內存數據.UnSafe類在於sun.misc包中,其內部方法操作可以向C的指針一樣直接操作內存,因為Java中CAS操作的助興依賴於UNSafe類的方法.
注意UnSafe類中所有的方法都是native修飾的,也就是說UnSafe類中的方法都是直接調用操作底層資源執行響應的任務
2.變量ValueOffset,便是該變量在內存中的偏移地址,因為UnSafe就是根據內存偏移地址獲取數據的
3.變量value和volatile修飾,保證了多線程之間的可見性.
3.CAS是什么
unSafe.getAndIncrement
var1 AtomicInteger對象本身.var2 該對象值的引用地址var4 需要變動的數值var5 是用過var1 var2找出內存中紳士的值用該對象當前的值與var5比較如果相同,更新var5的值並且返回true如果不同,繼續取值然后比較,直到更新完成
假設線程A和線程B兩個線程同時執行getAndAddInt操作(分別在不同的CPU上):
1.AtomicInteger里面的value原始值為3,即主內存中AtomicInteger的value為3,根據JMM模型,線程A和線程B各自持有一份值為3的value的副本分別到各自的工作內存.
2.線程A通過getIntVolatile(var1,var2) 拿到value值3,這是線程A被掛起.
3.線程B也通過getIntVolatile(var1,var2) 拿到value值3,此時剛好線程B沒有被掛起並執行compareAndSwapInt方法比較內存中的值也是3 成功修改內存的值為4 線程B打完收工 一切OK.
4.這是線程A恢復,執行compareAndSwapInt方法比較,發現自己手里的數值和內存中的數字4不一致,說明該值已經被其他線程搶先一步修改了,那A線程修改失敗,只能重新來一遍了.
5.線程A重新獲取value值,因為變量value是volatile修飾,所以其他線程對他的修改,線程A總是能夠看到,線程A繼續執行compareAndSwapInt方法進行比較替換,直到成功.
底層匯編
簡單版小總結
CAS (CompareAndSwap)比較當前工作內存中的值和內存中的值,如果相同則執行規定操作,否則繼續比較直到主內存和工作內存中的值一致為止
CAS應用
CAS有3個操作數,內存值V,舊的預期值A,要修改的更新值B ,當且僅當A和內存值V相同時,將內存值V修改為B,否則什么都不做。
4.CAS缺點
循環時間長開銷很大
只能保證一個共享變量的原子性
引出來ABA問題
原子類
AtomicInteger的ABA問題談談?原子更新引用知道嗎
ABA問題的產生
原子引用
AtomicReferenceDemo
@Getter
@Setter
@AllArgsConstructor
@ToStringclass User{
private String name;
private int age;
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
User zs = new User("zs", 22);
User ls = new User("ls", 22);
AtomicReference<User> userAtomicReference = new AtomicReference<>();
userAtomicReference.set(zs);
System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
}
}
時間戳原子引用
AtomicStampedReference

/** * Description: ABA問題的解決 * * @author veliger @163.com * @date 2019-04-12 21:30 **/ public class ABADemo { private static AtomicReference<Integer> atomicReference=new AtomicReference<>(100); private static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1); public static void main(String[] args) { System.out.println("===以下是ABA問題的產生==="); new Thread(()->{ atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ //先暫停1秒 保證完成ABA try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get()); },"t2").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("===以下是ABA問題的解決==="); new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第1次版本號"+stamp+"\t值是"+stampedReference.getReference()); //暫停1秒鍾t3線程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第2次版本號"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference()); stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第3次版本號"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference()); },"t3").start(); new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第1次版本號"+stamp+"\t值是"+stampedReference.getReference()); //保證線程3完成1次ABA try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本號"+stampedReference.getStamp()); System.out.println("最新的值\t"+stampedReference.getReference()); },"t4").start(); }
ArrayList線程非安全
我們知道ArrayList是線程不安全,請編寫一個不安全的案例並給出解決方案

package collection; import org.junit.Test; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/16/19 8:24 PM * @Version 1.0 * @Description: **/ public class ContainerNotSafeDemo { public static void main(String[] args) { List<String> strings = Arrays.asList("a", "b", "c"); strings.forEach(System.out::println); // ArrayList<Object> objects = new ArrayList<>(); // List<Object> objects = new Vector<Object>(); // List<String> objects = Collections.synchronizedList(new ArrayList<>()); List<String> objects = new CopyOnWriteArrayList<>(); for (int i = 0; i < 3; i++) { new Thread(()->{ objects.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(objects); },String.valueOf(i)).start(); } } @Test public void demo1() { ArrayList integers = new ArrayList<>(); for (int i = 0; i <5; i++) { new Thread(()->{ integers.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(integers); },"Thread"+i).start(); } try { TimeUnit.SECONDS.sleep(2); }catch (Exception e){ e.printStackTrace(); } System.out.println(integers); } }
公平鎖和非公平鎖
公平鎖 是指多個線程按照申請鎖的順序來獲取鎖類似排隊打飯 先來后到非公平鎖 是指在多線程獲取鎖的順序並不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取到鎖,在高並發的情況下,有可能造成優先級反轉或者飢餓現象
公平鎖/非公平鎖 並發包ReentrantLock的創建可以指定構造函數的boolean類型來得到公平鎖或者非公平鎖 默認是非公平鎖
Java ReentrantLock而言,通過構造哈數指定該鎖是否是公平鎖 默認是非公平鎖 非公平鎖的優點在於吞吐量必公平鎖大. 對於synchronized而言 也是一種非公平鎖.
可重入鎖(又名遞歸鎖)
可重入鎖(遞歸鎖):指的是同一線程外層函數獲得鎖之后,內層遞歸函數仍然能獲取該鎖的代碼,在同一線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取該鎖,也即是說:線程可以進入任何一個它已經擁有鎖同步着的代碼塊
ReentrantLock/synchronized就是一個典型的可重入鎖 可重入鎖最大的作用就是避免死鎖
ReenterLockDemo

package lock; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/16/19 10:42 PM * @Version 1.0 * @Description: * 可重入鎖(也叫做遞歸鎖) * 指的是同一先生外層函數獲得鎖后,內層敵對函數任然能獲取該鎖的代碼 * 在同一線程外外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖 * 也就是說,線程可以進入任何一個它已經標記的鎖所同步的代碼塊 **/ class Phone { public synchronized void sendSms() throws Exception { System.out.println(Thread.currentThread().getName() + "\tsendSms"); sendEmail(); } public synchronized void sendEmail() throws Exception { System.out.println(Thread.currentThread().getName() + "\tsendEmail"); } } public class ReenterLockDemo { /** * t1 sendSms * t1 sendEmail * t2 sendSms * t2 sendEmail * @param args */ public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { try { phone.sendSms(); } catch (Exception e) { e.printStackTrace(); } }, "t1").start(); new Thread(() -> { try { phone.sendSms(); } catch (Exception e) { e.printStackTrace(); } }, "t2").start(); } }

package collection; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/16/19 10:55 PM * @Version 1.0 * @Description: 可重入鎖(也叫做遞歸鎖) * 指的是同一先生外層函數獲得鎖后,內層敵對函數任然能獲取該鎖的代碼 * 在同一線程外外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖 * 也就是說,線程可以進入任何一個它已經標記的鎖所同步的代碼塊 **/ class Phone implements Runnable { private Lock lock = new ReentrantLock(); @Override public void run() { get(); } private void get() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\tget"); set(); } finally { lock.unlock(); } } private void set() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\tset"); } finally { lock.unlock(); } }} public class ReenterLockDemo { /** * Thread-0 get * Thread-0 set * Thread-1 get * Thread-1 set * @param args * */ public static void main(String[] args) { Phone phone = new Phone(); Thread t3 = new Thread(phone); Thread t4 = new Thread(phone); t3.start(); t4.start(); }}
自旋鎖
自旋鎖(spinlock):是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗cpu
SpinLockDemo

package lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/17/19 8:53 AM * @Version 1.0 * @Description: **/ public class SpinLockDemo { //原子引用線程 AtomicReference<Thread> atomicReference = new AtomicReference<>(); public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"\t come in AvA"); while (!atomicReference.compareAndSet(null,thread)){ } } public void myUnlock(){ Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread,null); System.out.println(Thread.currentThread().getName()+"\t invoked myUnlock"); } public static void main(String[] args) { SpinLockDemo spinLockDemo = new SpinLockDemo(); new Thread(()->{ spinLockDemo.myLock(); try { TimeUnit.SECONDS.sleep(5); }catch (Exception e){ e.printStackTrace(); } spinLockDemo.myUnlock(); },"AA").start(); try { TimeUnit.SECONDS.sleep(1); }catch (Exception e){ e.printStackTrace(); } new Thread(()->{ spinLockDemo.myLock(); spinLockDemo.myUnlock(); },"BB").start(); } }
獨占鎖(寫)/共享鎖(讀)/互斥鎖
獨占鎖:指該鎖一次只能被一個線程所持有。對於Reentrant和Synchronized而言都是獨占鎖
共享鎖:指該鎖可被多個線程所持有
對於ReentrantReadWriteLock其讀鎖是共享鎖,其寫鎖是獨占鎖。
讀鎖的共享鎖可保證並發讀是非常有效的,讀寫,寫讀,寫寫的過程是互斥的
可重入讀寫鎖

/** * 資源類 */class MyCaChe { /** * 保證可見性 */ private volatile Map<String, Object> map = new HashMap<>(); private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); /** * 寫 * * @param key * @param value */ public void put(String key, Object value) { reentrantReadWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "\t正在寫入" + key); //模擬網絡延時 try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } map.put(key, value); System.out.println(Thread.currentThread().getName() + "\t正在完成"); } finally { reentrantReadWriteLock.writeLock().unlock(); } } /** * 讀 * * @param key */ public void get(String key) { reentrantReadWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "\t正在讀取"); //模擬網絡延時 try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } Object result = map.get(key); System.out.println(Thread.currentThread().getName() + "\t正在完成" + result); } finally { reentrantReadWriteLock.readLock().unlock(); } } public void clearCaChe() { map.clear(); }}/** * Description: * 多個線程同時操作 一個資源類沒有任何問題 所以為了滿足並發量 * 讀取共享資源應該可以同時進行 * 但是 * 如果有一個線程想去寫共享資源來 就不應該有其他線程可以對資源進行讀或寫 * <p> * 小總結: * 讀 讀能共存 * 讀 寫不能共存 * 寫 寫不能共存 * 寫操作 原子+獨占 整個過程必須是一個完成的統一整體 中間不允許被分割 被打斷 * * @author veliger@163.com * @date 2019-04-13 0:45 **/ public class ReadWriteLockDemo { public static void main(String[] args) { MyCaChe myCaChe = new MyCaChe(); for (int i = 1; i <= 5; i++) { final int temp = i; new Thread(() -> { myCaChe.put(temp + "", temp); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { int finalI = i; new Thread(() -> { myCaChe.get(finalI + ""); }, String.valueOf(i)).start(); } }}
CountDownLatch/CyclicBarrier/Semaphore
CountDownLatch
讓一些線程阻塞直到另外一些完成后才被喚醒
CountDownLatch主要有兩個方法,當一個或多個線程調用await方法時,調用線程會被阻塞.其他線程調用countDown方法計數器減1(調用countDown方法時線程不會阻塞),當計數器的值變為0,因調用await方法被阻塞的線程會被喚醒,繼續執行
CountDownLatchDemo

package lock; import java.util.concurrent.CountDownLatch; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/17/19 10:52 AM * @Version 1.0 * @Description: **/ public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i < 7; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t國,被滅"); countDownLatch.countDown(); },CountryEnum.forEach_CountryEnum(i).getRetMessage()).start(); } countDownLatch.await(); System.out.println(Thread.currentThread().getName()+"\t 秦國一統天下!"); } public static void closeDoor(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i < 6; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t 上完自習,離開教室"); countDownLatch.countDown(); },String.valueOf(i)).start(); } countDownLatch.await(); System.out.println(Thread.currentThread().getName()+"\t 班長最后關門走人!"); } }

package lock; public enum CountryEnum { ONE(1,"齊"),TWO(2,"楚"),THREE(3,"燕"),FOUR(4,"趙"),FIVE(5,"魏"),SIX(6,"韓"); private Integer retCode; private String retMessage; public Integer getRetCode() { return retCode; } public String getRetMessage() { return retMessage; } CountryEnum() { } CountryEnum(Integer retCode, String retMessage) { this.retCode = retCode; this.retMessage = retMessage; } public static CountryEnum forEach_CountryEnum(int index){ CountryEnum[] values = CountryEnum.values(); for (CountryEnum element:values) { if (index == element.getRetCode()){ return element; } } return null; } }
CyclicBarrier
CyclicBarrier的字面意思是可循環(Cyclic) 使用的屏障(barrier).它要做的事情是,讓一組線程到達一個屏障(也可以叫做同步點)時被阻塞,知道最后一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續干活,線程進入屏障通過CyclicBarrier的await()方法.
CyclicBarrierDemo
集齊7顆龍珠就能召喚神龍

package lock; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/17/19 11:45 AM * @Version 1.0 * @Description: **/ public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { System.out.println("召喚神龍"); }); for (int i = 0; i < 7; i++) { final int tmp = i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t收集到第:"+tmp+"顆龍珠"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } } }
Semaphore
信號量的主要用戶兩個目的,一個是用於多喝共享資源的相互排斥使用,另一個用於並發資源數的控制.

package lock; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/17/19 11:59 AM * @Version 1.0 * @Description: **/ public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3);//三個停車位 for (int i = 0; i < 6; i++) {//6部車 new Thread(()->{ try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"\t搶到車位"); try { TimeUnit.SECONDS.sleep(3); }catch (Exception e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t停車三秒后離開車位"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); }finally { } },String.valueOf(i)).start(); } } }
阻塞隊列
隊列+阻塞隊列
阻塞隊列,顧名思義,首先它是一個隊列,而一個阻塞隊列在數據結構中所起的作用大致如圖所示:\
線程1往阻塞隊列中添加元素二線程2從隊列中移除元素
當阻塞隊列是空時,從隊列中獲取元素的操作將會被阻塞.
當阻塞隊列是滿時,往隊列中添加元素的操作將會被阻塞.同樣試圖往已滿的阻塞隊列中添加新圓度的線程同樣也會被阻塞,
知道其他線程從隊列中移除一個或者多個元素或者全清空隊列后使隊列重新變得空閑起來並后續新增.
為什么用?有什么好處?
在多線程領域:所謂阻塞,在某些情況下會掛起線程(即線程阻塞),一旦條件滿足,被掛起的線程優惠被自動喚醒為什么需要使用BlockingQueue好處是我們不需要關心什么時候需要阻塞線程,
什么時候需要喚醒線程,因為BlockingQueue都一手給你包辦好了在concurrent包 發布以前,
在多線程環境下,我們每個程序員都必須自己去控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的復雜度.
BlockingQueue的核心方法
拋出異常當阻塞隊列滿時,再往隊列里面add插入元素會拋IllegalStateException: Queue full當阻塞隊列空時,再往隊列Remove元素時候回拋出NoSuchElementException特殊值插入方法,成功返回true 失敗返回false移除方法,
成功返回元素,隊列里面沒有就返回null一直阻塞當阻塞隊列滿時,生產者繼續往隊列里面put元素,隊列會一直阻塞直到put數據or響應中斷退出當阻塞隊列空時,消費者試圖從隊列take元素,隊列會一直阻塞消費者線程直到隊列可用.超時退出當阻塞隊列滿時,
隊列會阻塞生產者線程一定時間,超過后限時后生產者線程就會退出
架構梳理+種類分析
架構介紹
種類分析
ArrayBlockingQueue: 由數組結構組成的有界阻塞隊列.
LinkedBlockingDeque: 由鏈表結構組成的有界(但大小默認值Integer>MAX_VALUE)阻塞隊列.
PriorityBlockingQueue:支持優先級排序的無界阻塞隊列.
DelayQueue: 使用優先級隊列實現的延遲無界阻塞隊列.
SynchronousQueue:不存儲元素的阻塞隊列,也即是單個元素的隊列.
理論:SynchronousQueue沒有容量與其他BlcokingQueue不同,SynchronousQueue是一個不存儲元素的BlcokingQueue每個put操作必須要等待一個take操作,否則不能繼續添加元素,反之亦然.

package lock; import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/17/19 9:14 PM * @Version 1.0 * @Description: **/ public class SynchronousQueueDemo { public static void main(String[] args) { BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "\t put 1"); blockingQueue.put("1"); System.out.println(Thread.currentThread().getName() + "\t put 2"); blockingQueue.put("2"); System.out.println(Thread.currentThread().getName() + "\t put 3"); blockingQueue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } }, "AAA").start(); new Thread(() -> { try { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } }, "BBB").start(); }}
LinkedTransferQueue:由鏈表結構組成的無界阻塞隊列.
LinkedBlockingDeque:由了解結構組成的雙向阻塞隊列.
用在哪里
生產者消費者模式
傳統版

/** * 共享資源類 */ class ShareData { private int num = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() throws Exception { lock.lock(); try { //判斷 while (num != 0) { //等待 不生產 condition.await(); } //干活 num++; System.out.println(Thread.currentThread().getName() + "\t" + num); //通知喚醒 condition.signalAll(); } finally { lock.unlock(); } } public void deIncrement() throws Exception { lock.lock(); try { //判斷 while (num == 0) { //等待 不生產 condition.await(); } //干活 num--; System.out.println(Thread.currentThread().getName() + "\t" + num); //通知喚醒 condition.signalAll(); } finally { lock.unlock(); } }}/** * Description * 一個初始值為0的變量 兩個線程交替操作 一個加1 一個減1來5輪 * * @author veliger@163.com * @version 1.0 * @date 2019-04-13 14:01 **/ public class ProdConsumerTraditionDemo { public static void main(String[] args) { ShareData shareData = new ShareData(); new Thread(() -> { for (int i = 1; i <= 5; i++) { try { shareData.increment(); } catch (Exception e) { e.printStackTrace(); } } }, "AA").start(); new Thread(() -> { for (int i = 1; i <= 5; i++) { try { shareData.deIncrement(); } catch (Exception e) { e.printStackTrace(); } } }, "BB").start(); }}
阻塞隊列版

class MyResource { /** * 默認開啟 進行生產消費的交互 */ private volatile boolean flag = true; /** * 默認值是0 */ private AtomicInteger atomicInteger = new AtomicInteger(); private BlockingQueue<String> blockingQueue = null; public MyResource(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; System.out.println(blockingQueue.getClass().getName()); } public void myProd() throws Exception { String data = null; boolean returnValue; while (flag) { data = atomicInteger.incrementAndGet() + ""; returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS); if (returnValue) { System.out.println(Thread.currentThread().getName() + "\t 插入隊列數據" + data + "成功"); } else { System.out.println(Thread.currentThread().getName() + "\t 插入隊列數據" + data + "失敗"); } TimeUnit.SECONDS.sleep(1); } System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag); } public void myConsumer() throws Exception { String result = null; while (flag) { result = blockingQueue.poll(2L, TimeUnit.SECONDS); if(null==result||"".equalsIgnoreCase(result)){ flag=false; System.out.println(Thread.currentThread().getName()+"\t"+"超過2m沒有取到 消費退出"); System.out.println(); System.out.println(); return; } System.out.println(Thread.currentThread().getName() + "消費隊列" + result + "成功"); } } public void stop() throws Exception{ flag=false; }}/** * Description * volatile/CAS/atomicInteger/BlockQueue/線程交互/原子引用 * * @author veliger@163.com * @version 1.0 * @date 2019-04-13 14:02 **/ public class ProdConsumerBlockQueueDemo { public static void main(String[] args) throws Exception { MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10)); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t生產線程啟動"); try { myResource.myProd(); } catch (Exception e) { e.printStackTrace(); } },"Prod").start(); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t消費線程啟動"); try { myResource.myConsumer(); } catch (Exception e) { e.printStackTrace(); } },"consumer").start(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(); System.out.println(); System.out.println(); System.out.println("時間到,停止活動"); myResource.stop(); }}
線程池
消息中間件
Synchronized和Lock區別
1.原始構成 synchronized是關鍵字屬於JVM層面 monitorenter(底層是通過monitor對象來完成,其實wait/notify等方法也依賴monitor對象只有在同步塊或方法中才能調wait/notify等方法monitorexit) Lock是具體類(import java.util.concurrent.locks.Lock)是api層面的鎖 2.使用方法 synchronized是不需要用戶去手動釋放資源,當synchronized代碼執行完成后系統會自動讓線程釋放對鎖的占用 ReentrantLock 則需要用戶去手動釋放若沒有主動釋放就有可能導致出現死鎖的現象。需要lock()和unlock()方法配合try...catch..finally 語句來使用 3.等待是否可中斷 synchronized不可中斷,除非拋出異常或者正常運行完成 ReentrantLock 可中斷, 1.設置超時方法tryLock(Long timeout,TimeUnit unit) 2.lockInterruptibly() 放代碼塊中,調用interrupt()方法可中斷 4.加鎖是否公平 synchronized非公平鎖 ReentrantLock 兩者都可以,默認非公平鎖,構造方法可以傳入boolean值,true為公平鎖,false為非公平鎖 5.鎖綁定多個條件Condition synchronized沒有 ReentrantLock用來實現分組喚醒需要喚醒的線程們,可以精確喚醒,而不是像synchronized要么隨機喚醒一個要么喚醒全部線程
線程池 ThreadPoolExecutor
為什么使用線程池,優勢
線程池做的工作主要是控制運行的線程的數量,處理過程中將任務加入隊列,然后在線程創建后啟動這些任務,如果先生超過了最大數量,超出的數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來執行.
他的主要特點為:線程復用:控制最大並發數:管理線程.
第一:降低資源消耗.通過重復利用自己創建的線程降低線程創建和銷毀造成的消耗.
第二: 提高響應速度.當任務到達時,任務可以不需要等到線程和粗昂就愛你就能立即執行.
第三: 提高線程的可管理性.線程是稀缺資源,如果無限的創阿金,不僅會消耗資源,還會較低系統的穩定性,使用線程池可以進行統一分配,調優和監控.
線程池如何使用?
架構實現
Java中的線程池是通過Executor框架實現的,該框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個類.
編碼實現
了解
Executors.newCachedThreadPool(); java8新出: Executors.newWorkStealingPool(int); java8新增,使用目前機器上可以的處理器作為他的並行級別
重點
Executors.newFixedThreadPool(int)
主要特點如下:1.創建一個定長線程池,可控制線程的最大並發數,超出的線程會在隊列中等待.2.newFixedThreadPool創建的線程池corePoolSize和MaxmumPoolSize是 相等的,它使用的的LinkedBlockingQueue
執行一個長期的任務,性能好很多
Executors.newSingleThreadExecutor()
主要特點如下:1.創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務都按照指定順序執行.2.newSingleThreadExecutor將corePoolSize和MaxmumPoolSize都設置為1,它使用的的LinkedBlockingQueue
一個任務一個線程執行的任務場景
Executors.newCachedThreadPool()
主要特點如下:1.創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則創建新線程.2.newCachedThreadPool將corePoolSize設置為0MaxmumPoolSize設置為Integer.MAX_VALUE,它使用的是SynchronousQUeue,也就是說來了任務就創建線程運行,如果線程空閑超過60秒,就銷毀線程
適用:執行很多短期異步的小程序或者負載較輕的服務器
ThreadPoolExecutor
線程池七大重要參數

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
1.corePoolSize:線程池中的常駐核心線程數
1.在創建了線程池后,當有請求任務來之后,就會安排池中的線程去執行請求任務,近似理解為今日當值線程2.當線程池中的線程數目達到corePoolSize后,就會把到達的任務放入到緩存隊列當中.
2.maximumPoolSize:線程池能夠容納同時執行的最大線程數,此值大於等於1
3.keepAliveTime:多余的空閑線程存活時間,當空間時間達到keepAliveTime值時,多余的線程會被銷毀直到只剩下corePoolSize個線程為止
默認情況下:只有當線程池中的線程數大於corePoolSize時keepAliveTime才會起作用,知道線程中的線程數不大於corepoolSIze,
4.unit:keepAliveTime的單位
5.workQueue:任務隊列,被提交但尚未被執行的任務.
6.threadFactory:表示生成線程池中工作線程的線程工廠,用戶創建新線程,一般用默認即可
7.handler:拒絕策略,表示當線程隊列滿了並且工作線程大於等於線程池的最大顯示 數(maxnumPoolSize)時如何來拒絕.
線程池的底層工作原理
線程池參數配置
線程池用過嗎?生產上你是如何設置合理參數
線程池的拒絕策略請你談談
是什么
等待隊列也已經排滿了,再也塞不下新的任務了同時,線程池的max也到達了,無法接續為新任務服務這時我們需要拒絕策略機制合理的處理這個問題.
JDK內置的拒絕策略
AbortPolicy(默認):直接拋出RejectedException異常阻止系統正常運行 CallerRunPolicy:"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是 DiscardOldestPolicy:拋棄隊列中等待最久的任務,然后把當前任務加入隊列中嘗試再次提交 DiscardPolicy:直接丟棄任務,不予任何處理也不拋出異常.如果允許任務丟失,這是最好的拒絕策略
拒絕策略:
AbortPolicy(默認):直接拋出RejectedExecution異常阻止系統正常運行 CallerRunsPoliy:"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者,從而降低新任務的流量。 DiscardOldestPoliy:拋棄隊列中的等待最久的任務,然后把當前任務加入隊列中嘗試再次提交當前任務。 DiscardPolicy:直接丟棄任務,不給予任何處理也不拋異常。如果允許任務丟失,這是最好的一種方案。
以上內置策略均實現了RejectExecutionHandler接口
你在工作中單一的/固定數的/可變你的三種創建線程池的方法,你用哪個多?超級大坑
答案是一個都不用,我們生產上只能使用自定義的
參考阿里巴巴java開發手冊
【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明:使用線程池的好處是減少在創建和銷毀線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors返回的線程池對象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允許的請求隊列長度為Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
2)CachedThreadPool和ScheduledThreadPool:允許的創建線程數量為Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
你在工作中是如何創建線程池的,是否自定義過線程池使用

package ThreadPool; import java.util.concurrent.*; /** * @ProjectName SummaryOfInterviewTopics * @Author 麥奇 * @Email biaogejiushibiao@outlook.com * @Date 8/18/19 11:02 AM * @Version 1.0 * @Description: **/ public class ThreadPoolExecutorDemo { public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(3), Executors.defaultThreadFactory(), //默認拋出異常 // new ThreadPoolExecutor.DiscardPolicy()); new ThreadPoolExecutor.AbortPolicy()); // 回退調用者 // new ThreadPoolExecutor.CallerRunsPolicy() // 處理不來的不處理 // new ThreadPoolExecutor.DiscardOldestPolicy() // 模擬10個用戶來辦理業務 沒有用戶就是來自外部的請求線程. try { for (int i = 1; i <= 15; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t 辦理業務"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } //threadPoolInit(); } private static void threadPoolInit() { /** * 一池5個處理線程 */ //ExecutorService threadPool= Executors.newFixedThreadPool(5); /** * * 一池一線程 */ //ExecutorService threadPool= Executors.newSingleThreadExecutor(); /** * 一池N線程 */ ExecutorService threadPool = Executors.newCachedThreadPool(); //模擬10個用戶來辦理業務 沒有用戶就是來自外部的請求線程. try { for (int i = 1; i <= 20; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t 辦理業務"); }); try { TimeUnit.MICROSECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } }
合理配置線程池你是如何考慮的?
CPU密集型
System.out.println(Runtime.getRuntime().availableProcessors());查看CPU核數
IO密集型
死鎖編碼及定位分析
是什么
產生死鎖的主要原因
系統資源不足
進程運行推進的順序不合適
資源分配不當
代碼

class HoldThread implements Runnable { private String lockA; private String lockB; public HoldThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "\t 自己持有鎖" + lockA + "嘗試獲得" + lockB); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "\t 自己持有鎖" + lockB + "嘗試獲得" + lockA); } } }}/** * Description: * 死鎖是指兩個或者以上的進程在執行過程中, * 因爭奪資源而造成的一種相互等待的現象, * 若無外力干涉那他們都將無法推進下去 * * @author veliger@163.com * @date 2019-04-14 0:05 **/ public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new HoldThread(lockA, lockB), "threadAAA").start(); new Thread(new HoldThread(lockB, lockA), "threadBBB").start(); }}
解決
jps命令定位進程編號
jstack找到死鎖查看
Java里面鎖請談談你的理解能說多少說多少
JVM+GC
1.JVM體系結構
2垃圾收集器