JMM 自己是一種抽象的概念並非真實存在,它描述的是一組規定或則規範,經過這組規範定義了程序中的訪問方式。java
JMM 同步規定api
因爲 JVM 運行程序的實體是線程,而每一個線程建立時 JVM 都會爲其建立一個工做內存,工做內存是每一個線程的私有數據區域,而 Java 內存模型中規定全部變量的儲存在主內存,主內存是共享內存區域,全部的線程均可以訪問,但線程對變量的操做(讀取賦值等)必須都工做內存進行看。數組
首先要將變量從主內存拷貝的本身的工做內存空間,而後對變量進行操做,操做完成後再將變量寫回主內存,不能直接操做主內存中的變量,工做內存中存儲着主內存中的變量副本拷貝,前面說過,工做內存是每一個線程的私有數據區域,所以不一樣的線程間沒法訪問對方的工做內存,線程間的通訊(傳值)必須經過主內存來完成。緩存
內存模型圖安全
可見性數據結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * @Author: cuzz * @Date: 2019/4/16 21:29 * @Description: 可見性代碼實例 */ public class VolatileDemo { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " coming..."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } data.addOne(); System.out.println(Thread.currentThread().getName() + " updated..."); }).start(); while (data.a == 0) { // looping } System.out.println(Thread.currentThread().getName() + " job is done..."); } } class Data { // int a = 0; volatile int a = 0; void addOne() { this.a += 1; } } |
若是不加 volatile 關鍵字,則主線程會進入死循環,加 volatile 則主線程可以退出,說明加了 volatile 關鍵字變量,當有一個線程修改了值,會立刻被另外一個線程感知到,當前值做廢,重新從主內存中獲取值。對其餘線程可見,這就叫可見性。多線程
原子性架構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class VolatileDemo { public static void main(String[] args) { // test01(); test02(); } // 測試原子性 private static void test02() { Data data = new Data(); for (int i = 0; i < 20; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { data.addOne(); } }).start(); } // 默認有 main 線程和 gc 線程 while (Thread.activeCount() > 2) { Thread.yield(); } System.out.println(data.a); } } class Data { volatile int a = 0; void addOne() { this.a += 1; } } |
發現並不能輸入 20000併發
有序性dom
計算機在執行程序時,爲了提升性能,編譯器個處理器經常會對指令作重排,通常分爲如下 3 種
單線程環境裏面確保程序最終執行的結果和代碼執行的結果一致
處理器在進行重排序時必須考慮指令之間的數據依賴性
多線程環境中線程交替執行,因爲編譯器優化重排的存在,兩個線程中使用的變量可否保證用的變量可否一致性是沒法肯定的,結果沒法預測
代碼示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class ReSortSeqDemo { int a = 0; boolean flag = false; public void method01() { a = 1; // flag = true; // ----線程切換---- flag = true; // a = 1; } public void method02() { if (flag) { a = a + 3; System.out.println("a = " + a); } } } |
若是兩個線程同時執行,method01 和 method02 若是線程 1 執行 method01 重排序了,而後切換的線程 2 執行 method02 就會出現不同的結果。
volatile 實現禁止指令重排序的優化,從而避免了多線程環境下程序出現亂序的現象
先了解一個概念,內存屏障(Memory Barrier)又稱內存柵欄,是一個 CPU 指令,他的做用有兩個:
因爲編譯器個處理器都能執行指令重排序優化,若是在指令間插入一條 Memory Barrier 則會告訴編譯器和 CPU,無論什麼指令都不能個這條 Memory Barrier 指令重排序,也就是說經過插入內存屏障禁止在內存屏障先後執行重排序優化。內存屏障另外一個做用是強制刷出各類 CPU 緩存數據,所以任何 CPU 上的線程都能讀取到這些數據的最新版本。
下面是保守策略下,volatile寫插入內存屏障後生成的指令序列示意圖:
下面是在保守策略下,volatile讀插入內存屏障後生成的指令序列示意圖:
多線程環境下可能存在的安全問題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@NotThreadSafe public class Singleton01 { private static Singleton01 instance = null; private Singleton01() { System.out.println(Thread.currentThread().getName() + " construction..."); } public static Singleton01 getInstance() { if (instance == null) { instance = new Singleton01(); } return instance; } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(()-> Singleton01.getInstance()); } executorService.shutdown(); } } |
發現構造器裏的內容會屢次輸出
雙重鎖單例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Singleton02 { private static volatile Singleton02 instance = null; private Singleton02() { System.out.println(Thread.currentThread().getName() + " construction..."); } public static Singleton02 getInstance() { if (instance == null) { synchronized (Singleton01.class) { if (instance == null) { instance = new Singleton02(); } } } return instance; } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(()-> Singleton02.getInstance()); } executorService.shutdown(); } } |
若是沒有加 volatile 就不必定是線程安全的,緣由是指令重排序的存在,加入 volatile 能夠禁止指令重排。
緣由是在於某一個線程執行到第一次檢測,讀取到的 instance 不爲 null 時,instance 的引用對象可能尚未完成初始化。
instance = new Singleton()
能夠分爲如下三步完成
1 2 3 |
memory = allocate(); // 1.分配對象空間 instance(memory); // 2.初始化對象 instance = memory; // 3.設置instance指向剛分配的內存地址,此時instance != null |
步驟 2 和步驟 3 不存在依賴關係,並且不管重排前仍是重排後程序的執行結果在單線程中並無改變,所以這種優化是容許的。
發生重排
1 2 3 |
memory = allocate(); // 1.分配對象空間 instance = memory; // 3.設置instance指向剛分配的內存地址,此時instance != null,但對象尚未初始化完成 instance(memory); // 2.初始化對象 |
因此不加 volatile 返回的實例不爲空,但多是未初始化的實例
1 2 3 4 5 6 7 8 9 10 11 |
public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(666); // 獲取真實值,並替換爲相應的值 boolean b = atomicInteger.compareAndSet(666, 2019); System.out.println(b); // true boolean b1 = atomicInteger.compareAndSet(666, 2020); System.out.println(b1); // false atomicInteger.getAndIncrement(); } } |
1 2 3 4 5 6 7 8 |
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } |
引出一個問題:UnSafe 類是什麼?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { // 獲取下面 value 的地址偏移量 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; // ... } |
CAS 的全稱 Compare-And-Swap,它是一條 CPU 併發。
它的功能是判斷內存某一個位置的值是否爲預期,若是是則更改這個值,這個過程就是原子的。
CAS 併發原體如今 JAVA 語言中就是 sun.misc.Unsafe 類中的各個方法。調用 UnSafe 類中的 CAS 方法,JVM 會幫咱們實現出 CAS 彙編指令。這是一種徹底依賴硬件的功能,經過它實現了原子操做。因爲 CAS 是一種系統源語,源語屬於操做系統用語範疇,是由若干條指令組成,用於完成某一個功能的過程,而且原語的執行必須是連續的,在執行的過程當中不容許被中斷,也就是說 CAS 是一條原子指令,不會形成所謂的數據不一致的問題。
分析一下 getAndAddInt 這個方法
1 2 3 4 5 6 7 8 |
// unsafe.getAndAddInt public final int getAndAddInt(Object obj, long valueOffset, long expected, int val) { int temp; do { temp = this.getIntVolatile(obj, valueOffset); // 獲取快照值 } while (!this.compareAndSwap(obj, valueOffset, temp, temp + val)); // 若是此時 temp 沒有被修改,就能退出循環,不然從新獲取 return temp; } |
原子引用
1 2 3 4 5 6 7 8 9 10 |
public class AtomicReferenceDemo { public static void main(String[] args) { User cuzz = new User("cuzz", 18); User faker = new User("faker", 20); AtomicReference<User> atomicReference = new AtomicReference<>(); atomicReference.set(cuzz); System.out.println(atomicReference.compareAndSet(cuzz, faker)); // true System.out.println(atomicReference.get()); // User(userName=faker, age=20) } } |
ABA 問題是怎麼產生的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/** * @program: learn-demo * @description: ABA * @author: cuzz * @create: 2019-04-21 23:31 **/ public class ABADemo { private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); public static void main(String[] args) { new Thread(() -> { atomicReference.compareAndSet(100, 101); atomicReference.compareAndSet(101, 100); }).start(); new Thread(() -> { // 保證上面線程先執行 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } atomicReference.compareAndSet(100, 2019); System.out.println(atomicReference.get()); // 2019 }).start(); } } |
當有一個值從 A 改成 B 又改成 A,這就是 ABA 問題。
時間戳原子引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package com.cuzz.thread; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; /** * @program: learn-demo * @description: ABA * @author: cuzz * @create: 2019-04-21 23:31 **/ public class ABADemo2 { private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) { new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + " 的版本號爲:" + stamp); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 ); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 ); }).start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + " 的版本號爲:" + stamp); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(b); // false System.out.println(atomicStampedReference.getReference()); // 100 }).start(); } } |
咱們先保證兩個線程的初始版本爲一致,後面修改是因爲版本不同就會修改失敗。
故障現象
1 2 3 4 5 6 7 8 9 10 11 12 |
public class ContainerDemo { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); Random random = new Random(); for (int i = 0; i < 100; i++) { new Thread(() -> { list.add(random.nextInt(10)); System.out.println(list); }).start(); } } } |
發現報 java.util.ConcurrentModificationException
致使緣由
解決方案
new Vector();
Collections.synchronizedList(new ArrayList<>());
new CopyOnWriteArrayList<>();
優化建議
是什麼
代碼實現
可重入鎖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class ReentrantLock { boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException { Thread thread = Thread.currentThread(); while (isLocked && lockedBy != thread) { wait(); } isLocked = true; lockedCount++; lockedBy = thread; } public synchronized void unlock() { if (Thread.currentThread() == lockedBy) { lockedCount--; if (lockedCount == 0) { isLocked = false; notify(); } } } } |
測試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Count { // NotReentrantLock lock = new NotReentrantLock(); ReentrantLock lock = new ReentrantLock(); public void print() throws InterruptedException{ lock.lock(); doAdd(); lock.unlock(); } private void doAdd() throws InterruptedException { lock.lock(); // do something System.out.println("ReentrantLock"); lock.unlock(); } public static void main(String[] args) throws InterruptedException { Count count = new Count(); count.print(); } } |
發現能夠輸出 ReentrantLock,咱們設計兩個線程調用 print() 方法,第一個線程調用 print() 方法獲取鎖,進入 lock() 方法,因爲初始 lockedBy 是 null,因此不會進入 while 而掛起當前線程,而是是增量 lockedCount 並記錄 lockBy 爲第一個線程。接着第一個線程進入 doAdd() 方法,因爲同一進程,因此不會進入 while 而掛起,接着增量 lockedCount,當第二個線程嘗試lock,因爲 isLocked=true,因此他不會獲取該鎖,直到第一個線程調用兩次 unlock() 將 lockCount 遞減爲0,纔將標記爲 isLocked 設置爲 false。
不可重入鎖
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class NotReentrantLock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while (isLocked) { wait(); } isLocked = true; } public synchronized void unlock() { isLocked = false; notify(); } } |
測試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Count { NotReentrantLock lock = new NotReentrantLock(); public void print() throws InterruptedException{ lock.lock(); doAdd(); lock.unlock(); } private void doAdd() throws InterruptedException { lock.lock(); // do something lock.unlock(); } public static void main(String[] args) throws InterruptedException { Count count = new Count(); count.print(); } } |
當前線程執行print()方法首先獲取lock,接下來執行doAdd()方法就沒法執行doAdd()中的邏輯,必須先釋放鎖。這個例子很好的說明了不可重入鎖。
synchronized 和 ReentrantLock 都是可重入鎖
synchronzied
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class SynchronziedDemo { private synchronized void print() { doAdd(); } private synchronized void doAdd() { System.out.println("doAdd..."); } public static void main(String[] args) { SynchronziedDemo synchronziedDemo = new SynchronziedDemo(); synchronziedDemo.print(); // doAdd... } } |
上面能夠說明 synchronized 是可重入鎖。
ReentrantLock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class ReentrantLockDemo { private Lock lock = new ReentrantLock(); private void print() { lock.lock(); doAdd(); lock.unlock(); } private void doAdd() { lock.lock(); lock.lock(); System.out.println("doAdd..."); lock.unlock(); lock.unlock(); } public static void main(String[] args) { ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo(); reentrantLockDemo.print(); } } |
上面例子能夠說明 ReentrantLock 是可重入鎖,並且在 #doAdd 方法中加兩次鎖和解兩次鎖也能夠。
是指定嘗試獲取鎖的線程不會當即堵塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減小線程上線文切換的消耗,缺點就是循環會消耗 CPU。
手動實現自旋鎖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public class SpinLock { private AtomicReference<Thread> atomicReference = new AtomicReference<>(); private void lock () { System.out.println(Thread.currentThread() + " coming..."); while (!atomicReference.compareAndSet(null, Thread.currentThread())) { // loop } } private void unlock() { Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread, null); System.out.println(thread + " unlock..."); } public static void main(String[] args) throws InterruptedException { SpinLock spinLock = new SpinLock(); new Thread(() -> { spinLock.lock(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hahaha"); spinLock.unlock(); }).start(); Thread.sleep(1); new Thread(() -> { spinLock.lock(); System.out.println("hehehe"); spinLock.unlock(); }).start(); } } |
輸出:
1 2 3 4 5 6 |
Thread[Thread-0,5,main] coming... Thread[Thread-1,5,main] coming... hahaha Thread[Thread-0,5,main] unlock... hehehe Thread[Thread-1,5,main] unlock... |
獲取鎖的時候,若是原子引用爲空就獲取鎖,不爲空表示有人獲取了鎖,就循環等待。
是什麼
對於 ReentrantLock 和 synchronized 都是獨佔鎖;對與 ReentrantReadWriteLock 其讀鎖是共享鎖而寫鎖是獨佔鎖。讀鎖的共享可保證併發讀是很是高效的,讀寫、寫讀和寫寫的過程是互斥的。
讀寫鎖例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class MyCache { private volatile Map<String, Object> map = new HashMap<>(); private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); WriteLock writeLock = lock.writeLock(); ReadLock readLock = lock.readLock(); public void put(String key, Object value) { try { writeLock.lock(); System.out.println(Thread.currentThread().getName() + " 正在寫入..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } map.put(key, value); System.out.println(Thread.currentThread().getName() + " 寫入完成,寫入結果是 " + value); } finally { writeLock.unlock(); } } public void get(String key) { try { readLock.lock(); System.out.println(Thread.currentThread().getName() + " 正在讀..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Object res = map.get(key); System.out.println(Thread.currentThread().getName() + " 讀取完成,讀取結果是 " + res); } finally { readLock.unlock(); } } } |
測試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class ReadWriteLockDemo { public static void main(String[] args) { MyCache cache = new MyCache(); for (int i = 0; i < 5; i++) { final int temp = i; new Thread(() -> { cache.put(temp + "", temp + ""); }).start(); } for (int i = 0; i < 5; i++) { final int temp = i; new Thread(() -> { cache.get(temp + ""); }).start(); } } } |
輸出結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Thread-0 正在寫入... Thread-0 寫入完成,寫入結果是 0 Thread-1 正在寫入... Thread-1 寫入完成,寫入結果是 1 Thread-2 正在寫入... Thread-2 寫入完成,寫入結果是 2 Thread-3 正在寫入... Thread-3 寫入完成,寫入結果是 3 Thread-4 正在寫入... Thread-4 寫入完成,寫入結果是 4 Thread-5 正在讀... Thread-7 正在讀... Thread-8 正在讀... Thread-6 正在讀... Thread-9 正在讀... Thread-5 讀取完成,讀取結果是 0 Thread-7 讀取完成,讀取結果是 2 Thread-8 讀取完成,讀取結果是 3 Thread-6 讀取完成,讀取結果是 1 Thread-9 讀取完成,讀取結果是 4 |
能保證讀寫、寫讀和寫寫的過程是互斥的時候是獨享的,讀讀的時候是共享的。
讓一些線程堵塞直到另外一個線程完成一系列操做後才被喚醒。CountDownLatch 主要有兩個方法,當一個或多個線程調用 await 方法時,調用線程會被堵塞,其餘線程調用 countDown 方法會將計數減一(調用 countDown 方法的線程不會堵塞),當計數其值變爲零時,因調用 await 方法被堵塞的線程會被喚醒,繼續執行。
假設咱們有這麼一個場景,教室裏有班長和其餘6我的在教室上自習,怎麼保證班長等其餘6我的都走出教室在把教室門給關掉。
1 2 3 4 5 6 7 8 9 10 |
public class CountDownLanchDemo { public static void main(String[] args) { for (int i = 0; i < 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 離開了教室..."); }, String.valueOf(i)).start(); } System.out.println("班長把門給關了,離開了教室..."); } } |
此時輸出
1 2 3 4 5 6 7 |
0 離開了教室... 1 離開了教室... 2 離開了教室... 3 離開了教室... 班長把門給關了,離開了教室... 5 離開了教室... 4 離開了教室... |
發現班長都沒有等其餘人理他教室就把門給關了,此時咱們就可使用 CountDownLatch 來控制
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class CountDownLanchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i < 6; i++) { new Thread(() -> { countDownLatch.countDown(); System.out.println(Thread.currentThread().getName() + " 離開了教室..."); }, String.valueOf(i)).start(); } countDownLatch.await(); System.out.println("班長把門給關了,離開了教室..."); } } |
此時輸出
1 2 3 4 5 6 7 |
0 離開了教室... 1 離開了教室... 2 離開了教室... 3 離開了教室... 4 離開了教室... 5 離開了教室... 班長把門給關了,離開了教室... |
咱們假設有這麼一個場景,每輛車只能坐我的,當車滿了,就發車。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () -> { System.out.println("車滿了,開始出發..."); }); for (int i = 0; i < 8; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 開始上車..."); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } } |
輸出結果
1 2 3 4 5 6 7 8 9 10 |
Thread-0 開始上車... Thread-1 開始上車... Thread-3 開始上車... Thread-4 開始上車... 車滿了,開始出發... Thread-5 開始上車... Thread-7 開始上車... Thread-2 開始上車... Thread-6 開始上車... 車滿了,開始出發... |
假設咱們有 3 個停車位,6 輛車去搶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3); for (int i = 0; i < 6; i++) { new Thread(() -> { try { semaphore.acquire(); // 獲取一個許可 System.out.println(Thread.currentThread().getName() + " 搶到車位..."); Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + " 離開車位"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 釋放一個許可 } }).start(); } } } |
輸出
1 2 3 4 5 6 7 8 9 10 11 12 |
Thread-1 搶到車位... Thread-2 搶到車位... Thread-0 搶到車位... Thread-2 離開車位 Thread-0 離開車位 Thread-3 搶到車位... Thread-1 離開車位 Thread-4 搶到車位... Thread-5 搶到車位... Thread-3 離開車位 Thread-5 離開車位 Thread-4 離開車位 |
當阻塞隊列是滿時,往隊列裏添加元素的操做將會被阻塞。
核心方法
| 方法\行爲 | 拋異常 | 特定的值 | 阻塞 | 超時 |
| :——-: | :——-: | :—————: | :—-: | :————————-: |
| 插入方法 | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
| 移除方法 | | poll()、remove(o) | take() | poll(timeout, timeunit) |
| 檢查方法 | element() | peek() | | |
行爲解釋:
拋異常:若是操做不能立刻進行,則拋出異常
特定的值:若是操做不能立刻進行,將會返回一個特殊的值,通常是 true 或者 false
阻塞:若是操做不能立刻進行,操做會被阻塞
超時:若是操做不能立刻進行,操做會被阻塞指定的時間,若是指定時間沒執行,則返回一個特殊值,通常是 true 或者 false
插入方法:
刪除方法:
檢查方法:
SynchronousQueue,實際上它不是一個真正的隊列,由於它不會爲隊列中元素維護存儲空間。與其餘隊列不一樣的是,它維護一組線程,這些線程在等待着把元素加入或移出隊列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class SynchronousQueueDemo { public static void main(String[] args) { SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>(); new Thread(() -> { try { synchronousQueue.put(1); Thread.sleep(3000); synchronousQueue.put(2); Thread.sleep(3000); synchronousQueue.put(3); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { Integer val = synchronousQueue.take(); System.out.println(val); Integer val2 = synchronousQueue.take(); System.out.println(val2); Integer val3 = synchronousQueue.take(); System.out.println(val3); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } |
線程池用於多線程處理中,它能夠根據系統的狀況,能夠有效控制線程執行的數量,優化運行效果。線程池作的工做主要是控制運行的線程的數量,處理過程當中將任務放入隊列,而後在線程建立後啓動這些任務,若是線程數量超過了最大數量,那麼超出數量的線程排隊等候,等其它線程執行完畢,再從隊列中取出任務來執行。
主要特色爲:
主要優勢
繼承 Thread
實現 Runnable 接口
實現 Callable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 在 FutureTask 中傳入 Callable 的實現類 FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { return 666; } }); // 把 futureTask 放入線程中 new Thread(futureTask).start(); // 獲取結果 Integer res = futureTask.get(); System.out.println(res); } } |
ThreadPoolExecutor做爲java.util.concurrent包對外提供基礎實現,之內部線程池的形式對外提供管理任務執行,線程調度,線程池管理等等服務。
參數 | 做用 |
---|---|
corePoolSize | 核心線程池大小 |
maximumPoolSize | 最大線程池大小 |
keepAliveTime | 線程池中超過 corePoolSize 數目的空閒線程最大存活時間;能夠allowCoreThreadTimeOut(true) 使得核心線程有效時間 |
TimeUnit | keepAliveTime 時間單位 |
workQueue | 阻塞任務隊列 |
threadFactory | 新建線程工廠 |
RejectedExecutionHandler | 當提交任務數超過 maxmumPoolSize+workQueue 之和時,任務會交給RejectedExecutionHandler 來處理 |
說說線程池的底層工做原理?
重點講解: 其中比較容易讓人誤解的是:corePoolSize,maximumPoolSize,workQueue之間關係。
當線程池小於corePoolSize時,新提交任務將建立一個新線程執行任務,即便此時線程池中存在空閒線程。
當線程池達到corePoolSize時,新提交任務將被放入 workQueue 中,等待線程池中任務調度執行。
當workQueue已滿,且 maximumPoolSize 大於 corePoolSize 時,新提交任務會建立新線程執行任務。
當提交任務數超過 maximumPoolSize 時,新提交任務由 RejectedExecutionHandler 處理。
當線程池中超過corePoolSize 線程,空閒時間達到 keepAliveTime 時,關閉空閒線程 。
當設置allowCoreThreadTimeOut(true) 時,線程池中 corePoolSize 線程空閒時間達到 keepAliveTime 也將關閉。
若是讀者對Java中的阻塞隊列有所瞭解的話,看到這裏或許就可以明白緣由了。
Java中的BlockingQueue主要有兩種實現,分別是ArrayBlockingQueue 和 LinkedBlockingQueue。
ArrayBlockingQueue是一個用數組實現的有界阻塞隊列,必須設置容量。
LinkedBlockingQueue是一個用鏈表實現的有界阻塞隊列,容量能夠選擇進行設置,不設置的話,將是一個無邊界的阻塞隊列,最大長度爲Integer.MAX_VALUE。
這裏的問題就出在:不設置的話,將是一個無邊界的阻塞隊列,最大長度爲Integer.MAX_VALUE。也就是說,若是咱們不設置LinkedBlockingQueue的容量的話,其默認容量將會是Integer.MAX_VALUE。
而newFixedThreadPool中建立LinkedBlockingQueue時,並未指定容量。此時,LinkedBlockingQueue就是一個無邊界隊列,對於一個無邊界隊列來講,是能夠不斷的向隊列中加入任務的,這種狀況下就有可能由於任務過多而致使內存溢出問題。
上面提到的問題主要體如今newFixedThreadPool和newSingleThreadExecutor兩個工廠方法上,並非說newCachedThreadPool和newScheduledThreadPool這兩個方法就安全了,這兩種方式建立的最大線程數多是Integer.MAX_VALUE,而建立這麼多線程,必然就有可能致使OOM。
自定義線程池
1 2 3 4 5 6 7 8 9 |
public class ThreadPoolExecutorDemo { public static void main(String[] args) { Executor executor = new ThreadPoolExecutor(2, 3, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy()); } } |
產生死鎖的緣由
代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; DeadLockDemo deadLockDemo = new DeadLockDemo(); Executor executor = Executors.newFixedThreadPool(2); executor.execute(() -> deadLockDemo.method(lockA, lockB)); executor.execute(() -> deadLockDemo.method(lockB, lockA)); } public void method(String lock1, String lock2) { synchronized (lock1) { System.out.println(Thread.currentThread().getName() + "--獲取到:" + lock1 + "; 嘗試獲取:" + lock2); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("獲取到兩把鎖!"); } } } } |
解決
jps -l 命令查定位進程號
1 2 3 4 5 |
28519 org.jetbrains.jps.cmdline.Launcher 32376 com.intellij.idea.Main 28521 com.cuzz.thread.DeadLockDemo 27836 org.jetbrains.kotlin.daemon.KotlinCompileDaemon 28591 sun.tools.jps.Jps |
jstack 28521 找到死鎖查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
2019-05-07 00:04:15 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode): "Attach Listener" #13 daemon prio=9 os_prio=0 tid=0x00007f7acc001000 nid=0x702a waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE // ... Found one Java-level deadlock: ============================= "pool-1-thread-2": waiting to lock monitor 0x00007f7ad4006478 (object 0x00000000d71f60b0, a java.lang.String), which is held by "pool-1-thread-1" "pool-1-thread-1": waiting to lock monitor 0x00007f7ad4003be8 (object 0x00000000d71f60e8, a java.lang.String), which is held by "pool-1-thread-2" Java stack information for the threads listed above: =================================================== "pool-1-thread-2": at com.cuzz.thread.DeadLockDemo.method(DeadLockDemo.java:34) - waiting to lock <0x00000000d71f60b0> (a java.lang.String) - locked <0x00000000d71f60e8> (a java.lang.String) at com.cuzz.thread.DeadLockDemo.lambda$main$1(DeadLockDemo.java:21) at com.cuzz.thread.DeadLockDemo$$Lambda$2/2074407503.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) "pool-1-thread-1": at com.cuzz.thread.DeadLockDemo.method(DeadLockDemo.java:34) - waiting to lock <0x00000000d71f60e8> (a java.lang.String) - locked <0x00000000d71f60b0> (a java.lang.String) at com.cuzz.thread.DeadLockDemo.lambda$main$0(DeadLockDemo.java:20) at com.cuzz.thread.DeadLockDemo$$Lambda$1/558638686.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock. |
最後發現一個死鎖。