互联网大厂高频重点面试题
1.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(); } }} View Code?
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實例未必完成初始化,也就造成了線程安全問題. View Code?
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方法進行比較替換,直到成功.
?
底層匯編
?
簡單版小總結
?
4.CAS缺點
循環時間長開銷很大
只能保證一個共享變量的原子性
引出來ABA問題???
?
原子類AtomicInteger的ABA問題談談?原子更新引用知道嗎
ABA問題的產生
?
原子引用
AtomicReferenceDemo
/** * Description: * * @author veliger@163.com * @date 2019-04-12 21:23 **/@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(); } ABADemo我們知道ArrayList是線程不安全,請編寫一個不安全的案例并給出解決方案
公平鎖和非公平鎖
公平鎖 是指多個線程按照申請鎖的順序來獲取鎖類似排隊打飯 先來后到非公平鎖 是指在多線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取到鎖,在高并發的情況下,有可能造成優先級反轉或者饑餓現象?
公平鎖/非公平鎖 并發包ReentrantLock的創建可以指定構造函數的boolean類型來得到公平鎖或者非公平鎖 默認是非公平鎖 Java ReentrantLock而言,通過構造哈數指定該鎖是否是公平鎖 默認是非公平鎖 非公平鎖的優點在于吞吐量必公平鎖大. 對于synchronized而言 也是一種非公平鎖.可重入鎖(又名遞歸鎖)
ReentrantLock/synchronized就是一個典型的可重入鎖 可重入鎖最大的作用就是避免死鎖?
ReenterLockDemo
package cn.atguigu.interview.study.thread; 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"); }}/** * Description: * 可重入鎖(也叫做遞歸鎖) * 指的是同一先生外層函數獲得鎖后,內層敵對函數任然能獲取該鎖的代碼 * 在同一線程外外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖 * * 也就是說,線程可以進入任何一個它已經標記的鎖所同步的代碼塊 * * @author veliger@163.com * @date 2019-04-12 23:36 **/ 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(); }} 參考1 package cn.atguigu.interview.study.thread;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock; 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(); } }}/** * Description: * 可重入鎖(也叫做遞歸鎖) * 指的是同一先生外層函數獲得鎖后,內層敵對函數任然能獲取該鎖的代碼 * 在同一線程外外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖 * <p> * 也就是說,線程可以進入任何一個它已經標記的鎖所同步的代碼塊 * * @author veliger@163.com * @date 2019-04-12 23:36 **/ 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(); }} 參考2自旋鎖
SpinLockDemo
獨占鎖(寫)/共享鎖(讀)/互斥鎖
/** * 資源類 */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(); } }} ReadWriteLockDemo?
讀寫鎖
?
?
CountDownLatch/CyclicBarrier/Semaphore
CountDownLatch
讓一些線程阻塞直到另外一些完成后才被喚醒
CountDownLatch主要有兩個方法,當一個或多個線程調用await方法時,調用線程會被阻塞.其他線程調用countDown方法計數器減1(調用countDown方法時線程不會阻塞),當計數器的值變為0,因調用await方法被阻塞的線程會被喚醒,繼續執行CountDownLatchDemo
public class CountDownLatchDemo { public static void main(String[] args) throws Exception { closeDoor(); } /** * 關門案例 * @throws InterruptedException */ private static void closeDoor() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; 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班長鎖門離開教室"); } } 關門案例 /** * Description * 枚舉的使用 * * @author veliger@163.com * @version 1.0 * @date 2019-04-13 10:14 **/ public enum CountryEnum { /** * */ ONE(1, "齊"), /** * */ TWO(2, "楚"), /** * */ THREE(3, "燕"), /** * */ FOUR(4, "趙"), /** * */ FIVE(5, "魏"), /** * */ SIX(6, "韓"); CountryEnum(Integer code, String name) { this.code = code; this.name = name; } @Getter private Integer code; @Getter private String name; public static CountryEnum forEach(int index) { CountryEnum[] countryEnums = CountryEnum.values(); for (CountryEnum countryEnum : countryEnums) { if (index == countryEnum.getCode()) { return countryEnum; } } return null; }} 枚舉的使用 public class CountDownLatchDemo { public static void main(String[] args) throws Exception { sixCountry(); } /** * 秦滅六國 一統華夏 * @throws InterruptedException */ private static void sixCountry() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t" + "國,滅亡"); countDownLatch.countDown(); }, CountryEnum.forEach(i).getName()).start(); } countDownLatch.await(); System.out.println("秦統一"); } } 秦滅六國CyclicBarrier
CyclicBarrier的字面意思是可循環(Cyclic) 使用的屏障(barrier).它要做的事情是,讓一組線程到達一個屏障(也可以叫做同步點)時被阻塞,知道最后一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續干活,線程進入屏障通過CyclicBarrier的await()方法.?
CyclicBarrierDemo
集齊7顆龍珠就能召喚神龍
public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{ System.out.println("召喚神龍"); }); for (int i = 1; i <=7; i++) { final int temp = i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t 收集到第"+ temp +"顆龍珠"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },String.valueOf(i)).start(); } }} code?
Semaphore
信號量的主要用戶兩個目的,一個是用于多喝共享資源的相互排斥使用,另一個用于并發資源數的控制. /** * Description * * @author veliger@163.com * @version 1.0 * @date 2019-04-13 11:08 **/ public class SemaphoreDemo { public static void main(String[] args) { //模擬3個停車位 Semaphore semaphore = new Semaphore(3); //模擬6部汽車 for (int i = 1; i <= 6; i++) { new Thread(() -> { try { //搶到資源 semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "\t搶到車位"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t 停3秒離開車位"); } catch (InterruptedException e) { e.printStackTrace(); } finally { //釋放資源 semaphore.release(); } }, 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操作,否則不能繼續添加元素,反之亦然. /** * Description * 阻塞隊列SynchronousQueue演示 * * @author veliger@163.com * @version 1.0 * @date 2019-04-13 13:49 **/ 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(); }} SynchronousQueueDemo?
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(); }} ProdConsumerTraditionDemo?
阻塞隊列版
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(); }} ProdConsumerBlockQueueDemo?
線程池
消息中間件
?
線程池? ? 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
?
線程池七大重要參數
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:直接丟棄任務,不予任何處理也不拋出異常.如果允許任務丟失,這是最好的拒絕策略?
?以上內置策略均實現了RejectExecutionHandler接口
?你在工作中單一的/固定數的/可變你的三種創建線程池的方法,你用哪個多?超級大坑
答案是一個都不用,我們生產上只能使用自定義的 參考阿里巴巴java開發手冊 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:使用線程池的好處是減少在創建和銷毀線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。說明:Executors返回的線程池對象的弊端如下:1)FixedThreadPool和SingleThreadPool:允許的請求隊列長度為Integer.MAX_VALUE,
可能會堆積大量的請求,從而導致OOM。2)CachedThreadPool和ScheduledThreadPool:允許的創建線程數量為Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
?
你在工作中是如何創建線程池的,是否自定義過線程池使用
public class MyThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(3), Executors.defaultThreadFactory(), //默認拋出異常 //new ThreadPoolExecutor.AbortPolicy() //回退調用者 //new ThreadPoolExecutor.CallerRunsPolicy() //處理不來的不處理 //new ThreadPoolExecutor.DiscardOldestPolicy() new ThreadPoolExecutor.DiscardPolicy() ); //模擬10個用戶來辦理業務 沒有用戶就是來自外部的請求線程. try { for (int i = 1; i <= 10; 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(); } }} Case?
?合理配置線程池你是如何考慮的?
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(); }} View Code?
解決
jps命令定位進程編號 jstack找到死鎖查看?
Java里面鎖請談談你的理解能說多少說多少
?
轉載于:https://www.cnblogs.com/biaogejiushibiao/p/11269179.html
總結
以上是生活随笔為你收集整理的互联网大厂高频重点面试题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Silverlight学习(一) 创建S
- 下一篇: 【CF1063B】Labyrinth [