
獲取CPU核數
(資料圖)
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/15 * @description 測試 */public class Test { public static void main(String[] args) { // 獲取CPU核數 // CPU 密集型,IO密集型 System.out.println(Runtime.getRuntime().availableProcessors()); }}
線程有幾個狀態
public enum State { // 新生 NEW, // 運行 RUNNABLE, // 阻塞 BLOCKED, // 等待 WAITING, // 超時等待 TIMED_WAITING, // 終止 TERMINATED;}
wait/sleep區別
1、來自不同的類
wait => Object
sleep => Thread
2、關于鎖的釋放
wait會釋放鎖,sleep不會釋放鎖
3、使用范圍是不同的
wait必須在同步代碼塊中
sleep可以在任何地方執行
4、是否需要捕獲異常
wait 不需要捕獲異常
sleep 必須要捕獲異常
傳統 Synchronized
線程就是一個單獨的資源類,沒有任何附屬操作:
屬性和方法/** * @author java小豪 * @version 1.0.0 * @date 2022/12/15 * @description 基本賣票 */public class SaleTicketDemo01 { public static void main(String[] args) { // 真正的多線程開發,公司中的開發 // 線程就是一個單獨的資源類,沒有任何附屬操作 Ticket ticket = new Ticket(); // lambda表達式 () -> {} new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 60; i++) { ticket.sale(); } }, "C").start(); }}/**資源類 OOP編程**/class Ticket { /**屬性、方法**/ private int number = 50; /** * 賣票方式 * synchronized 本質:隊列,鎖 */ public synchronized void sale() { if (number > 0) { System.out.println(Thread.currentThread().getName() + "賣出了" + (number--) + "票,剩余:"+ number); } }}
Lock 接口
ReentrantLock的源碼:
公平鎖:十分公平,可以先來后到
非公平鎖:十分不公平,可以插隊(默認)
Lock鎖實現線程安全:
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/15 * @description 賣票2 */public class SaleTicketDemo02 { public static void main(String[] args) { // 真正的多線程開發,公司中的開發 // 線程就是一個單獨的資源類,沒有任何附屬操作 Ticket2 ticket = new Ticket2(); // lambda表達式 () -> {} new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "A").start(); new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "B").start(); new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "C").start(); }}/** * Lock三部曲 * 1. new ReentrantLock(); * 2. lock.Lock(); // 加鎖 * 3. finally => lock.unlock(); // 解鎖 */class Ticket2 { /**屬性、方法**/ private int number = 50; Lock lock = new ReentrantLock(); /** * 賣票方式 * synchronized 本質:隊列,鎖 */ public void sale() { // 加鎖 lock.lock(); try { // 業務代碼 if (number > 0) { System.out.println(Thread.currentThread().getName() + "賣出了" + (number--) + "票,剩余:"+ number); } } catch (Exception e) { throw new RuntimeException(e); } finally { // 解鎖 lock.unlock(); } }}
Synchronized 和 Lock 區別
1、Synchronized 內置Java關鍵字,Lock一個Java類
2、Synchronized 無法判斷鎖的狀態,Lock 可以判斷是否獲取到了鎖
3、Synchronized 會自動釋放鎖,lock必須要手動釋放鎖!如果不釋放鎖,就會產生死鎖
4、Synchronized 線程1(獲得鎖, 阻塞) 和 線程2(等待, 一直等待),Lock鎖就不一定會等待(lock.tryLock();嘗試獲取鎖)
5、Synchronized 可重入鎖,不可以中斷的,非公平的;Lock,可重入鎖,可以判斷鎖,非公平(可以自己設置)
6、Synchronized 適合鎖少量的代碼同步問題,Lock適合鎖大量的同步代碼問題
鎖是什么, 如何判斷鎖的是誰?
面試必會:單例模式、排序算法、生產者和消費者、死鎖
生產者和消費者問題 Synchronized 版
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/15 * @description 生產者和消費者測試1 */public class Test1 { // 線程之間的通信問題:生產者和消費者 等待喚醒和通知喚醒 public static void main(String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i <10; i++) { try { data.increment(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "A").start(); new Thread(() -> { for (int i = 0; i <10; i++) { try { data.decrement(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "B").start(); }}/** * 判斷等待, 業務, 通知 */class Data { private int number = 0; /** * +1 */ public synchronized void increment() throws InterruptedException { if (number != 0) { // 等待操作 this.wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知其他線程,我 +1 完畢了 this.notifyAll(); } /** * -1 */ public synchronized void decrement() throws InterruptedException { if (number == 0) { this.wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知其他線程,我 -1 完畢了 this.notifyAll(); }}
問題存在, A、B、C、D
存在虛假喚醒
if 改為 while防止虛假喚醒
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/15 * @description 生產者和消費者測試1 */public class Test1 { // 線程之間的通信問題:生產者和消費者 等待喚醒和通知喚醒 public static void main(String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i <10; i++) { try { data.increment(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "A").start(); new Thread(() -> { for (int i = 0; i <10; i++) { try { data.decrement(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "B").start(); new Thread(() -> { for (int i = 0; i <10; i++) { try { data.increment(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "C").start(); new Thread(() -> { for (int i = 0; i <10; i++) { try { data.decrement(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "D").start(); }}/** * 判斷等待, 業務, 通知 */class Data { private int number = 0; /** * +1 */ public synchronized void increment() throws InterruptedException { while (number != 0) { // 等待操作 this.wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知其他線程,我 +1 完畢了 this.notifyAll(); } /** * -1 */ public synchronized void decrement() throws InterruptedException { while (number == 0) { this.wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知其他線程,我 -1 完畢了 this.notifyAll(); }}
JUC版的生產者和消費者問題
通過Lock了解到Condition
代碼實現:
package com.chen.pc;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/16 * @description 生產者和消費者測試2 */public class Test2 { // 線程之間的通信問題:生產者和消費者 等待喚醒和通知喚醒 public static void main(String[] args) { Data1 data = new Data1(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "D").start(); }}/** * 判斷等待, 業務, 通知 */class Data1 { private int number = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); /** * +1 */ public void increment() throws InterruptedException { lock.lock(); try { while (number != 0) { // 等待操作 condition.await(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知其他線程,我 +1 完畢了 condition.signalAll(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } /** * -1 */ public void decrement() throws InterruptedException { lock.lock(); try { while (number == 0) { condition.await(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); // 通知其他線程,我 -1 完畢了 condition.signalAll(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } }}
Condition 精準的通知和喚醒線程
代碼實現:
package com.chen.pc;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/16 * @description Condition實現精準喚醒和通知 */public class Test3 { public static void main(String[] args) { Data2 data = new Data2(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.printA(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.printB(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { data.printC(); } }, "C").start(); }}/** * A 執行完調用B, B執行完調用C, C執行完調用A */class Data2 { private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int number = 1; public void printA() { lock.lock(); try { // 業務代碼 判斷 -> 執行 -> 通知 while (number != 1) { // 等待 condition1.await(); } System.out.println(Thread.currentThread().getName() + "AAAAAAA"); // 喚醒B number = 2; condition2.signal(); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); } } public void printB() { lock.lock(); try { // 業務代碼 判斷 -> 執行 -> 通知 while (number != 2) { // 等待 condition2.await(); } System.out.println(Thread.currentThread().getName() + "BBBBBBB"); number = 3; condition3.signal(); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); } } public void printC() { lock.lock(); try { // 業務代碼 判斷 -> 執行 -> 通知 while (number != 3) { // 等待 condition3.await(); } System.out.println(Thread.currentThread().getName() + "CCCCCCC"); number = 1; condition1.signal(); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); } }}
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/16 * @description 8鎖現象的八個問題 ** 1、標準情況下,兩個線程先打印 發短信還是 打電話? 1/發短信 2/打電話 * 2、sendSms延遲4秒,兩個線程先打印 發短信還是 打電話? 1/發短信 2/打電話 *
*/public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); // 鎖的存在 new Thread(() -> { phone.sendSms(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { phone.call(); }, "B").start(); }}class Phone { // synchronized 鎖的對象是方法調用者 // 兩個方法用的是同一個鎖,誰想拿到誰執行 public synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("發短信"); } public synchronized void call() { System.out.println("打電話"); }}
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/16 * @description 8鎖現象解釋 ** 3、增加了一個普通方法,先執行發短信還是Hello 普通方法 * 4、兩個對象,兩個同步方法 //打電話 *
*/public class Test2 { public static void main(String[] args) { // 兩個對象 Phone2 phone = new Phone2(); Phone2 phone2 = new Phone2(); // 鎖的存在 new Thread(() -> { phone.sendSms(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { phone2.call(); }, "B").start(); }}class Phone2 { /** * synchronized 鎖的對象是方法調用者 */ public synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("發短信"); } public synchronized void call() { System.out.println("打電話"); } /** * 這里沒有鎖,不是同步方法,不受鎖的影響 */ public void hello() { System.out.println("hello"); }}
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/16 * @description 8鎖現象測試 ** 5、增加兩個靜態的同步方法, 只有一個對象先打印 發短信? 打電話? // 發短信 * 6、兩個對象!增加兩個靜態的同步方法,先打印 發短信? 打電話? // 發短信 *
*/public class Test3 { public static void main(String[] args) { Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); // 鎖的存在 new Thread(() -> { phone1.sendSms(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { phone2.call(); }, "B").start(); }}class Phone3 { /** * synchronized 鎖的對象是方法調用者 * static 靜態方法 * 類一加載就有了! 鎖的是Class */ public static synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("發短信"); } public static synchronized void call() { System.out.println("打電話"); }}
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/16 * @description 8鎖現象解釋 ** 7、一個靜態同步方法,一個普通同步方法, 一個對象 先打印 發短信? 打電話? // 打電話 * 8、一個靜態同步方法,一個普通同步方法, 兩個對象 先打印 發短信? 打電話? // 打電話 *
*/public class Test4 { public static void main(String[] args) { Phone4 phone1 = new Phone4(); Phone4 phone2 = new Phone4(); // 鎖的存在 new Thread(() -> { phone1.sendSms(); }, "A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { phone2.call(); }, "B").start(); }}class Phone4 { /** * 靜態的同步方法 * 鎖的Class類模板 */ public static synchronized void sendSms() { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("發短信"); } /** * 普通的同步方法 * 鎖的調用者 */ public synchronized void call() { System.out.println("打電話"); }}
小結
new this 具體的一個手機
static Class 唯一的一個手機模板
List 不安全
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/16 * @description List不安全 ** java.util.ConcurrentModificationException 并發修改異常! *
*/public class ListTest { public static void main(String[] args) { // 并發下 ArrayList 不安全的 // Listlist1 = new ArrayList<>(); /** * 解決方法: * 1.List list = new Vector<>(); * 2.List list = Collections.synchronizedList(new ArrayList<>()); * 3.List list = new CopyOnWriteArrayList<>(); */ // CopyOnWrite 寫入時復制 COW 計算機程序設計領域的優化策略 // 多個線程調用的時候,list 讀取的時候,固定的, 寫入(覆蓋) // 寫入的時候避免覆蓋,造成數據問題 List list = new CopyOnWriteArrayList<>(); for (int i = 1; i <= 10; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); }, String.valueOf(i)).start(); } }}
Set 不安全
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/17 * @description Set不安全的 ** 1、java.util.ConcurrentModificationException 并發修改異常! * 解決方法: * 1、Set
*/public class SetTest { public static void main(String[] args) {// Setset = Collections.synchronizedSet(new HashSet<>()); * 2、 * set = new HashSet<>();// Set set = Collections.synchronizedSet(new HashSet<>()); Set set = new CopyOnWriteArraySet<>(); for (int i = 1; i <= 30; i++) { new Thread(() -> { set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); }, String.valueOf(i)).start(); } }}
HashSet 底層是什么?
private transient HashMapmap;public HashSet() { map = new HashMap<>();}// add set 本質就是 map key是無法重復的public boolean add(E e) { return map.put(e, PRESENT)==null;}
HashMap 不安全
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/17 * @description HashMap不安全 ** java.util.ConcurrentModificationException * 解決方法: * 1、Map
*/public class MapTest { public static void main(String[] args) { // map是這樣用的嗎? 工作中不用HashMap // 默認等價于什么? new HashMap<>(16, 0.75) // Mapmap = new ConcurrentHashMap<>(); * map = new HashMap<>(); Map map = new ConcurrentHashMap<>(); for (int i = 1; i <= 30; i++) { new Thread(() -> { map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); System.out.println(map); }, String.valueOf(i)).start(); } }}
ConcurrentHashMap的原理
/** * @throws NullPointerException if the specified key or value is null */public V put(@NotNull K key, @NotNull V value) { return putVal(key, value, false);}
1、可以有返回值
2、可以拋出異常
3、方法不同 run()/call()
代碼測試
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/20 * @description Callable測試類 */public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { // new Thread(new Runnable()).start(); // new Thread(new FutureTask()).start(); // new Thread(new FutureTask ( Callable )).start(); new Thread().start(); // 怎么啟動Callable MyThread thread = new MyThread(); FutureTask futureTask = new FutureTask(thread); new Thread(futureTask, "A").start(); // 結果會被緩存,效率高 new Thread(futureTask, "B").start(); // 該get方法可能會產生阻塞!把他放在最后、或者使用一步通信來處理 Integer o = (Integer) futureTask.get(); // 獲取callable的返回結果 System.out.println(o); }}class MyThread implements Callable { /** * Computes a result, or throws an exception if unable to do so */ @Override public Integer call() { System.out.println("call()"); return 1024; }}
?CountDownLatch?
?用給定的計數初始化。 ??await?
?方法阻塞,直到由于??countDown()?
?方法的調用而導致當前計數達到零,之后所有等待線程被釋放,并且任何后續的??await?
? 調用立即返回。 這是一個一次性的現象 - 計數無法重置。 如果您需要重置計數的版本,請考慮使用??CyclicBarrier?
? 。A ??CountDownLatch?
?是一種通用的同步工具,可用于多種用途。 一個??CountDownLatch?
?為一個計數的CountDownLatch用作一個簡單的開/關鎖存器,或者門:所有線程調用??await?
?在門口等待,直到被調用??countDown()?
?的線程打開。 一個??CountDownLatch?
?初始化N可以用來做一個線程等待,直到N個線程完成某項操作,或某些動作已經完成N次。??CountDownLatch?
?一個有用的屬性是,它不要求調用??countDown?
?線程等待計數到達零之前繼續,它只是阻止任何線程通過??await?
?,直到所有線程可以通過。import java.util.concurrent.CountDownLatch;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/20 * @description CountDownLatch測試 */public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException { // 總數是6, 必須要執行任務的時候,再使用! CountDownLatch downLatch = new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "Go out"); downLatch.countDown(); // 數量 -1 }, String.valueOf(i)).start(); } downLatch.await(); // 等待計數器歸0,然后再向下執行 System.out.println("Close Door"); }}
??downLatch.countDown(); ?
?// 數量-1
??downLatch.await();?
? // 等待計數器歸0,然后再向下執行
每次有線程調用 downLatch()數量-1,假設計數器變為0, downLatch.await()就會被喚醒,繼續執行
?CyclicBarrier?
?支持一個可選的??Runnable?
?命令,每個屏障點運行一次,在派對中的最后一個線程到達之后,但在任何線程釋放之前。 在任何一方繼續進行之前,此屏障操作對更新共享狀態很有用。import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/20 * @description CyclicBarrier測試 */public class CyclicBarrierTest { public static void main(String[] args) { /** * 集齊 7 顆龍珠召喚神龍 */ 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() + "收集" + temp + "顆龍珠"); try { cyclicBarrier.await(); // 等待 } catch (InterruptedException | BrokenBarrierException e) { throw new RuntimeException(e); } }).start(); } }}
?acquire()?
?都會阻塞,直到許可證可用,然后才能使用它。每個??release()?
?添加許可證,潛在地釋放阻塞獲取方。但是,沒有使用實際的許可證對象;??Semaphore?
?只保留可用數量的計數,并相應地執行。信號量通常用于限制線程數,而不是訪問某些(物理或邏輯)資源。在獲得項目之前,每個線程必須從信號量獲取許可證,以確保某個項目可用。 當線程完成該項目后,它將返回到池中,并將許可證返回到信號量,允許另一個線程獲取該項目。 請注意,當[調用??acquire()?
?時,不會保持同步鎖定,因為這將阻止某個項目返回到池中。 信號量封裝了限制對池的訪問所需的同步,與保持池本身一致性所需的任何同步分開。此類的構造函數可選擇接受公平參數。 當設置為false時,此類不會保證線程獲取許可的順序。 特別是, 闖入是允許的,也就是說,一個線程調用??acquire()?
?可以提前已經等待線程分配的許可證-在等待線程隊列的頭部邏輯新的線程將自己。 當公平設置為真時,信號量保證調用??acquire?
?方法的線程被選擇以按照它們調用這些方法的順序獲得許可(先進先出; FIFO)。 請注意,FIFO排序必須適用于這些方法中的特定內部執行點。 因此,一個線程可以在另一個線程之前調用??acquire?
? ,但是在另一個線程之后到達排序點,并且類似地從方法返回。 另請注意, 未定義的??tryAcquire?
?方法不符合公平性設置,但將采取任何可用的許可證。通常,用于控制資源訪問的信號量應該被公平地初始化,以確保線程沒有被訪問資源。 當使用信號量進行其他類型的同步控制時,非正常排序的吞吐量優勢往往超過公平性。import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/20 * @description Semaphore測試 */public class SemaphoreTest { public static void main(String[] args) { // 線程數量:停車位 Semaphore semaphore = new Semaphore(3); for (int i = 1; i <= 6; i++) { new Thread(() -> { // acquire() 得到 try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "搶到車位"); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName() + "離開車位"); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { // release() 釋放 semaphore.release(); } }, String.valueOf(i)).start(); } }}
??semaphore.acquire();?
?獲得,假設如果已經滿了,等待,等待被釋放為止
??semaphore.release();?
?釋放,會將當前的信號量釋放 +1 ,然后喚醒等待的線程
?ReadWriteLock?
?維護一對關聯的??locks?
? ,一個用于只讀操作,一個用于寫入。??read lock?
?可以由多個閱讀器線程同時進行,只要沒有作者。 ??write lock?
?是獨家的。所有??ReadWriteLock?
?實現必須保證的存儲器同步效應??writeLock?
?操作(如在指定??Lock?
?接口)也保持相對于所述相關聯的??readLock?
? 。 也就是說,一個線程成功獲取讀鎖定將會看到在之前發布的寫鎖定所做的所有更新。讀寫鎖允許訪問共享數據時的并發性高于互斥鎖所允許的并發性。 它利用了這樣一個事實:一次只有一個線程( 寫入線程)可以修改共享數據,在許多情況下,任何數量的線程都可以同時讀取數據(因此讀取器線程)。 從理論上講,通過使用讀寫鎖允許的并發性增加將導致性能改進超過使用互斥鎖。 實際上,并發性的增加只能在多處理器上完全實現,然后只有在共享數據的訪問模式是合適的時才可以。import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/20 * @description ReadWriteLock測試 ** 獨占鎖(寫鎖) 一次只能被一個線程占有 * 共享鎖(讀鎖) 多個線程可以同時占有 * 讀-讀 可以共存 * 讀-寫 不能共存 * 寫-寫 不能共存 *
*/public class ReadWriteLockTest { public static void main(String[] args) { MyCacheLock myCache = new MyCacheLock(); 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++) { final int temp = i; new Thread(() -> { myCache.get(temp + ""); }, String.valueOf(i)).start(); } }}/** * 加鎖的 */class MyCacheLock { private volatile Mapmap = new HashMap<>(); /**讀寫鎖,更細粒度的讀寫鎖**/ private ReadWriteLock lock = new ReentrantReadWriteLock(); // 存,寫,只希望同時只有一個線程寫 public void put(String key, Object value) { lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "寫入" + key); map.put(key, value); System.out.println(Thread.currentThread().getName() + "寫入完畢"); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.writeLock().unlock(); } } // 取,讀,所有人都可以讀 public void get(String key) { lock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "讀取" + key); map.get(key); System.out.println(Thread.currentThread().getName() + "讀取OK"); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.readLock().unlock(); } }}class MyCache { private volatile Map map = new HashMap<>(); // 存,寫 public void put(String key, Object value) { System.out.println(Thread.currentThread().getName() + "寫入" + key); map.put(key, value); System.out.println(Thread.currentThread().getName() + "寫入完畢"); } // 取,讀 public void get(String key) { System.out.println(Thread.currentThread().getName() + "讀取" + key); map.get(key); System.out.println(Thread.currentThread().getName() + "讀取OK"); }}
阻塞隊列:
BlockingQueue
使用隊列:添加、移除
四組API方式 | 拋出異常 | 不會拋出異常,有返回值 | 阻塞等待 | 超時等待 |
添加 | add() | offer() | put() | offer(,,) |
刪除 | remove() | poll() | take() | poll(,) |
判斷隊列隊頭 | element() | peek() | - | - |
拋出異常:
import java.util.concurrent.ArrayBlockingQueue;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/20 * @description 測試 */public class ArrayBlockingQueue1 { public static void main(String[] args) { test1(); } /** * 拋出異常 */ public static void test1() { // 隊列的大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.add("a")); System.out.println(blockingQueue.add("b")); System.out.println(blockingQueue.add("c")); // java.lang.IllegalStateException: Queue full 拋出異常! // System.out.println(blockingQueue.add("d")); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); // java.util.NoSuchElementException 拋出異常! // System.out.println(blockingQueue.remove()); }}
有返回值,不拋出異常:
/** * 有返回值,不拋出異常 */public static void test2() { // 隊列的大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.offer("a")); System.out.println(blockingQueue.offer("b")); System.out.println(blockingQueue.offer("c")); // System.out.println(blockingQueue.offer("d")); // FALSE! 不拋異常 System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); // null 不拋出異常}
阻塞等待:
/** * 等待,阻塞(一直阻塞) */public static void test3() throws InterruptedException { // 隊列的大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 一直阻塞 blockingQueue.put("a"); blockingQueue.put("b"); blockingQueue.put("c"); // blockingQueue.put("d"); // 隊列沒有位置了,一直阻塞 System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); // System.out.println(blockingQueue.take()); // 隊列為空,一直阻塞}
超時等待,自動退出:
/** * 等待,阻塞(超時退出) */public static void test4() throws InterruptedException { // 隊列的大小 ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); blockingQueue.offer("a"); blockingQueue.offer("b"); blockingQueue.offer("c"); blockingQueue.offer("d",2, TimeUnit.SECONDS); // 等待超過兩秒就退出 System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); blockingQueue.poll(2, TimeUnit.SECONDS);}
SynchronousQueue 同步隊列
import java.util.concurrent.BlockingQueue;import java.util.concurrent.SynchronousQueue;import java.util.concurrent.TimeUnit;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/21 * @description ** 同步隊列 和其他的BlockingQueue 不一樣,SynchronousQueue 不存儲元素 * put 一個元素,就必須從里面先take取出來,否則不能在put進去值 *
*/public class SynchronousQueueTest { public static void main(String[] args) { // 同步隊列 BlockingQueueblockingQueue = new SynchronousQueue<>(); new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + " put 1"); blockingQueue.put("1"); System.out.println(Thread.currentThread().getName() + " put 2"); blockingQueue.put("2"); System.out.println(Thread.currentThread().getName() + " put 3"); blockingQueue.put("3"); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "T1").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " = " +blockingQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " = " +blockingQueue.take()); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + " = " +blockingQueue.take()); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "T2").start(); }}
線程池:三大方法、7大參數、4種拒絕策略
線程池的好處:
1、降低資源的消耗
2、提高相應的速度
3、方便管理
線程復用、可以控制最大并發數,管理線程
線程池:三大方法
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/21 * @description 線程池的使用 */public class Demo01 { public static void main(String[] args) { // 單個線程 // ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 創建一個固定的線程池的大小 // ExecutorService threadPool = Executors.newFixedThreadPool(5); // 可伸縮的,遇強則強,遇弱則弱 ExecutorService threadPool = Executors.newCachedThreadPool(); try { for (int i = 0; i < 10; i++) { // 使用了線程池后,使用線程池來創建線程 threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " OK"); }); } } catch (Exception e) { throw new RuntimeException(e); } finally { // 使用線程池之后一定要關閉 threadPool.shutdown(); } }}
7大參數
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));}public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ());}public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue ());}// 本質ThreadPoolExecutorpublic ThreadPoolExecutor(int corePoolSize, // 核心線程池大小 int maximumPoolSize, // 最大核心線程池大小 long keepAliveTime, // 超時了沒有人調用就會釋放 TimeUnit unit, // 超時單位 BlockingQueue 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;}
手動創建線程池
import java.util.concurrent.*;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/21 * @description 線程池的使用 ** new ThreadPoolExecutor.AbortPolicy(): 線程池滿了之后,拋出異常 * new ThreadPoolExecutor.CallerRunsPolicy(): 當前線程執行 * new ThreadPoolExecutor.DiscardPolicy(): 隊列滿了,丟掉任務,不會拋出異常 * new ThreadPoolExecutor.DiscardOldestPolicy(): 隊列滿了,嘗試和最早的去競爭,也不會拋出異常 *
*/public class Demo01 { public static void main(String[] args) { // 單個線程 ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); try { // 最大承載: Deque + max // 超過 java.util.concurrent.RejectedExecutionException for (int i = 1; i <= 9; i++) { // 使用了線程池后,使用線程池來創建線程 threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " OK"); }); } } catch (Exception e) { throw new RuntimeException(e); } finally { // 使用線程池之后一定要關閉 threadPool.shutdown(); } }}
四種拒絕策略
// new ThreadPoolExecutor.AbortPolicy(): 線程池滿了之后,拋出異常// new ThreadPoolExecutor.CallerRunsPolicy(): 當前線程執行// new ThreadPoolExecutor.DiscardPolicy(): 隊列滿了,丟掉任務,不會拋出異常// new ThreadPoolExecutor.DiscardOldestPolicy(): 隊列滿了,嘗試和最早的去競爭,也不會拋出異常
最大線程如何定義:
CPU密集型:Runtime.getRuntime().availableProcessors(),IO密集型: 判斷程序中十分耗IO的線程,函數式接口:只有一個方法的接口
@FunctionalInterfacepublic interface Runnable { public abstract void run();}// foreach(消費者的函數式接口)
測試函數式(Function)接口:
import java.util.function.Function;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/21 * @description ** Function 函數型接口,有一個參數,一個返回類型 *
*/public class Demo01 { public static void main(String[] args) { /*Functionfunction = new Function () { @Override public String apply(String str) { return str; } };*/ Function function = (str) -> { return str; }; System.out.println(function.apply("abc")); }}
測試斷定型(Predicate)接口:
import java.util.function.Predicate;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/21 * @description 斷定型接口: 有一個參數輸入,返回值只能是 布爾值! */public class Demo02 { public static void main(String[] args) { /*// 判斷字符串是否為空 Predicatepredicate = new Predicate () { @Override public boolean test(String str) { return str.isEmpty(); } };*/ Predicate predicate = String::isEmpty; System.out.println(predicate.test("")); }}
Consumer 消費性接口
import java.util.function.Consumer;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/21 * @description Consumer消費型接口: 只有輸入, 沒有返回值 */public class Demo03 { public static void main(String[] args) { /*Consumerconsumer = new Consumer () { @Override public void accept(String str) { System.out.println(str); } };*/ Consumer consumer = (str) -> { System.out.println(str); }; consumer.accept("asd"); }}
Supplier 供給型接口
import java.util.function.Supplier;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/21 * @description Supplier供給型接口, 沒有參數,只有返回值 */public class Demo04 { public static void main(String[] args) { /*Suppliersupplier = new Supplier () { @Override public Integer get() { System.out.println("get()"); return 1024; } };*/ Supplier supplier = () -> {return 1024;}; System.out.println(supplier.get()); }}
import java.util.Arrays;import java.util.List;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 測試 ** 要求:現在有五個用戶!篩選 * 1、ID必須是偶數 * 2、年齡必須大于23歲 * 3、用戶名轉為大寫字母 * 4、用戶名字母倒著排序 * 5、只輸出一個用戶 *
*/public class Test { public static void main(String[] args) { User user1 = new User(1, "a", 21); User user2 = new User(2, "b", 22); User user3 = new User(3, "c", 23); User user4 = new User(4, "d", 24); User user5 = new User(6, "e", 25); Listlist = Arrays.asList(user1, user2, user3, user4, user5); // 鏈式編程 list.stream() .filter(u -> {return u.getId() % 2 == 0;}) .filter(user -> {return user.getAge() > 23;}) .map(user -> {return user.getName().toUpperCase();}) .sorted((u1, u2) -> {return u2.compareTo(u1);}) .limit(1) .forEach(System.out::println); }}
什么是ForkJoin
**ForkJoin:**在并行執行任務!提高效率,大數據量!把大任務拆成小任務
ForkJoin的特點:工作竊取
當本線程的任務執行完畢之后,回去找一部分其他線程未執行的任務去執行
ForkJoin里面維護的都是雙端隊列
ForkJoin的操作
import java.util.concurrent.RecursiveTask;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description ForkJoin測試 ** // 1、forkJoin 通過它來執行 * // 2、計算任務 forkJoinPool.execute(ForkJoinTask task) * // ForkJoinTask *
*/public class ForkJoinDemo extends RecursiveTask{ private Long start; private Long end; // 臨界值 private Long temp = 10000L; public ForkJoinDemo(Long start, Long end) { this.start = start; this.end = end; } /** * The main computation performed by this task. * * @return the result of the computation */ @Override protected Long compute() { if ((end - start) > temp) { // 分支合并計算 long sum = 0L; for (Long i = start; i <= end; i++) { sum += i; } return sum; } else { // forkJoin long mid = (start + end) / 2; ForkJoinDemo task1 = new ForkJoinDemo(start, mid); // 拆分任務 task1.fork(); ForkJoinDemo task2 = new ForkJoinDemo(mid + 1, end); task2.fork(); return task1.join() + task2.join(); } }}import java.util.concurrent.ExecutionException;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.ForkJoinTask;import java.util.stream.LongStream;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 測試 */public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { // test1(); // 676 // test2(); // 8909 test3(); // 289 } // public static void test1() { long sum = 0L; long start = System.currentTimeMillis(); for (long i = 1L; i <= 10_0000_0000; i++) { sum += i; } long end = System.currentTimeMillis(); System.out.println("sum = " + sum + "時間:" +(end - start)); } public static void test2() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask task = new ForkJoinDemo(1L, 10_0000_0000L); // 執行任務 // forkJoinPool.execute(task); ForkJoinTask submit = forkJoinPool.submit(task); Long sum = submit.get(); long end = System.currentTimeMillis(); System.out.println("sum = " + sum + "時間:" +(end - start)); } public static void test3() { long start = System.currentTimeMillis(); // Stream并行流 LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum); long end = System.currentTimeMillis(); System.out.println("sum = " + "時間:" +(end - start)); }}
import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 測試 ** 異步調用: CompletableFuture, 異步執行:成功回調、失敗回調 *
*/public class Demo01 { public static void main(String[] args) throws ExecutionException, InterruptedException { // 沒有返回值的異步回調// CompletableFuturecompletableFuture = CompletableFuture.runAsync(() -> {// try {// TimeUnit.SECONDS.sleep(2);// } catch (InterruptedException e) {// throw new RuntimeException(e);// }// System.out.println(Thread.currentThread().getName() + "runAsync => Void");// });// System.out.println("1111");// completableFuture.get(); // 有返回值的 supplyAsync 異步回調 CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName() + "supplyAsync => Integer"); return 1024; }); System.out.println(completableFuture.whenComplete((t, u) -> { System.out.println("t = " + t); // 正常的結果返回 System.out.println("u = " + u); // 錯誤信息:打印錯誤信息 }).exceptionally((e) -> { System.out.println(e.getMessage()); return 233; }).get()); }}
請你談談你對Volatile的理解
Volatile:是Java虛擬機提供輕量級的同步機制
1、保證可見性
2、不保證原子性
3、禁止指令重排
什么是JMM
JMM:Java內存模型,不存在的東西
關于JMM的一些同步約定:
1、線程解鎖前,必須把共享變量立刻刷回主存
2、線程加鎖前,必須讀取主存中的最新到工作內存中
3、加鎖和解鎖是同一把鎖
Java內存模型定義了以下八種操作來完成:
lock(鎖定):作用于主內存的變量,把一個變量標識為一條線程獨占狀態。unlock(解鎖):作用于主內存變量,把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定。read(讀取):作用于主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。use(使用):作用于工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。assign(賦值):作用于工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。store(存儲):作用于工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨后的write的操作。write(寫入):作用于主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中。Java內存模型還規定了在執行上述八種基本操作時,必須滿足如下規則:
如果要把一個變量從主內存中復制到工作內存,就需要按順尋地執行read和load操作, 如果把變量從工作內存中同步回主內存中,就要按順序地執行store和write操作。但Java內存模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。不允許read和load、store和write操作之一單獨出現不允許一個線程丟棄它的最近assign的操作,即變量在工作內存中改變了之后必須同步到主內存中。不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中。一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執行過了assign和load操作。一個變量在同一時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重復執行多次,多次執行lock后,只有執行相同次數的unlock操作,變量才會被解鎖。lock和unlock必須成對出現如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行load或assign操作初始化變量的值如果一個變量事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。對一個變量執行unlock操作之前,必須先把此變量同步到主內存中(執行store和write操作)。1、保證可見性
import java.util.concurrent.TimeUnit;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 測試 */public class JMMDemo { /** * 不加 volatile 程序會進入死循環 * 家加 volatile 可以保證可見性 */ private volatile static int num = 0; public static void main(String[] args) { new Thread(() -> { while (num == 0) { } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new RuntimeException(e); } num = 1; System.out.println(num); }}
2、不保證原子性
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 測試 ** 不保證原子性 *
*/public class VDemo02 { // volatile 不保證原子性 private volatile static int num = 0; public static void add() { num++; } public static void main(String[] args) { new Thread(() -> { for (int i = 1; i <= 200; i++) { for (int j = 0; j < 1000; j++) { add(); } } }).start(); while (Thread.activeCount() > 2) { Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); }}
如果不加lock和synchronized,怎么保證原子性?
使用原子類:解決原子性問題。
import java.util.concurrent.atomic.AtomicInteger;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 測試 ** 不保證原子性 *
*/public class VDemo02 { // volatile 不保證原子性 // 原子類的 Integer private volatile static AtomicInteger num = new AtomicInteger(); public static void add() {// num++; num.getAndIncrement(); // AtomicInteger 加一操作 } public static void main(String[] args) { new Thread(() -> { for (int i = 1; i <= 200; i++) { for (int j = 0; j < 1000; j++) { add(); } } }).start(); while (Thread.activeCount() > 2) { Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); }}
指令重排
什么是指令重排:源代碼 ——> 編譯器優化重排 ——> 指令并行也可能會重排 ——> 內存系統也會重排 ——> 執行
處理器在指令重排的時候,考慮:數據之間的依賴性!
volatile可以避免指令重排:
內存屏障。CPU指令。作用:
1、保證特定的操作執行順序!
2、可以保證某些變量的內存可見性
餓漢式
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 餓漢式單例 ** 創建即私有 *
*/public class Hungry { // 可能會造成空間浪費 byte[] data1 = new byte[1024 * 1024]; byte[] data2 = new byte[1024 * 1024]; byte[] data3 = new byte[1024 * 1024]; byte[] data4 = new byte[1024 * 1024]; private Hungry() { } private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance() { return HUNGRY; }}
DCL 懶漢式
import java.lang.reflect.Constructor;import java.lang.reflect.Field;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 懶漢式單例 */public class LazyMan { private static boolean chen = false; private LazyMan() { synchronized (LazyMan.class){ if (chen == false) { chen = true; } else { throw new RuntimeException("不要試圖用反射破壞異常"); } /*if (lazyMan != null) { throw new RuntimeException("不要試圖用反射破壞異常"); }*/ } System.out.println(Thread.currentThread().getName() + "ok"); } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { // 加鎖,雙重檢測鎖模式的 懶漢模式 DCL模式 if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { // 不是原子性操作 lazyMan = new LazyMan(); } } } return lazyMan; } // 反射 public static void main(String[] args) throws Exception { // LazyMan instance = LazyMan.getInstance(); Field chen1 = LazyMan.class.getDeclaredField("chen"); chen1.setAccessible(true); ConstructordeclaredConstructor = LazyMan.class.getDeclaredConstructor(String。class, int.class); declaredConstructor.setAccessible(true); LazyMan instance = declaredConstructor.newInstance(); chen1.set(instance, false); LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance2); }}// 1、分配內存空間// 2、執行構造方法,初始化對象// 3、把這個對象指向這個空間
靜態內部類
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 靜態內部類 */public class Holder { private Holder() { } public static Holder getInstance() { return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER = new Holder(); }}
單例不安全,反射:使用枚舉
/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 枚舉單例 */public enum EnumSingle { INSTANCE; public EnumSingle getInstance() { return INSTANCE; }}class Test { public static void main(String[] args) { EnumSingle instance1 = EnumSingle.INSTANCE; }}
什么是CAS
import java.util.concurrent.atomic.AtomicInteger;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description CAS測試 */public class CASDemo { // CAS compareAndSet: 比較并交換 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); // public final boolean compareAndSet(int expect, int update) 期望、更新 // 如果我期望的值拿到了就更新,否則就不更新 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); }}
Unsafe類
CAS: 比較并交換,比較當前工作中的值和主內存中的值,如果這個值是期望的,那么則執行操作!如果不是就一直循環!
缺點:
1、循環會耗時
2、一次性只能保證一個共享變量的原子性
3、ABA問題
CAS:ABA問題(貍貓換太子)
import java.util.concurrent.atomic.AtomicInteger;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description CAS測試 */public class CASDemo { // CAS compareAndSet: 比較并交換 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); // public final boolean compareAndSet(int expect, int update) 期望、更新 // 如果我期望的值拿到了就更新,否則就不更新 // ============= 搗亂的線程 ============== System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); // ============= 期望的線程 ============== System.out.println(atomicInteger.compareAndSet(2020, 6666)); System.out.println(atomicInteger.get()); }}
解決ABA問題,引入原子引用!對應的思想:樂觀鎖
帶版本號的 原子操作!
import java.util.Date;import java.util.Objects;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.AtomicStampedReference;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description CAS測試 */public class CASDemo { // CAS compareAndSet: 比較并交換 public static void main(String[] args) {// AtomicInteger atomicInteger = new AtomicInteger(2020); AtomicStampedReferenceatomicStampedReference = new AtomicStampedReference<>(123,1); // public final boolean compareAndSet(int expect, int update) 期望、更新 // 如果我期望的值拿到了就更新,否則就不更新 // ============= 搗亂的線程 ============== /*System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); // ============= 期望的線程 ============== System.out.println(atomicInteger.compareAndSet(2020, 6666)); System.out.println(atomicInteger.get());*/ new Thread(() -> { int stamp = atomicStampedReference.getStamp(); // 獲得版本號 System.out.println("a1 => " + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(atomicStampedReference.compareAndSet(123, 124, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a2 => " + atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(124, 123, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a3 => " + atomicStampedReference.getStamp()); }, "A").start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); // 獲得版本號 System.out.println("b1 => " + stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(atomicStampedReference.compareAndSet(123, 125, stamp, stamp + 1)); }, "B").start(); }}
Synchronized
package com.chen.lock;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 可重入鎖 * Synchronized */public class Demo1 { public static void main(String[] args) { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone1.sms(); }, "A").start(); new Thread(() -> { phone1.sms(); }, "B").start(); }}class Phone { public synchronized void sms() { System.out.println(Thread.currentThread().getName() + "sms()"); // call也有鎖 call(); } public synchronized void call() { System.out.println(Thread.currentThread().getName() + "call()"); }}
lock
package com.chen.lock;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 可重入鎖Lock版 */public class Demo2 { public static void main(String[] args) { Phone1 phone1 = new Phone1(); new Thread(() -> { phone1.sms(); }, "A").start(); new Thread(() -> { phone1.sms(); }, "B").start(); }}class Phone1 { Lock lock = new ReentrantLock(); public void sms() { // 獲得鎖 lock.lock(); // 細節:lock鎖必須配對,否則就會產生死鎖 try { System.out.println(Thread.currentThread().getName() + "sms()"); // call也有鎖 call(); } catch (Exception e) { throw new RuntimeException(e); } finally { // 釋放鎖 lock.unlock(); } } public void call() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "call()"); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); } }}
spinlock:
自定義自旋鎖測試:
package com.chen.lock;import java.util.concurrent.atomic.AtomicReference;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 自旋鎖 */public class SpinLockDemo { AtomicReferenceatomicReference = new AtomicReference<>(); // 加鎖 public void myLock() { Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + "===> myLock"); while (!atomicReference.compareAndSet(null, thread)) { } } // 解鎖 public void myUnLock() { Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + "===> myUnLock"); atomicReference.compareAndSet(thread, null); }}
測試:
package com.chen.lock;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 測試自旋鎖 */public class TestSpinLock { public static void main(String[] args) {// ReentrantLock reentrantLock = new ReentrantLock();// reentrantLock.lock();// reentrantLock.unlock(); // 底層使用自旋鎖 SpinLockDemo lock = new SpinLockDemo(); new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.myUnLock(); } }, "T1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.myUnLock(); } }, "T2").start(); }}
死鎖是什么?
死鎖測試
package com.chen.lock;import java.util.concurrent.TimeUnit;/** * @author java小豪 * @version 1.0.0 * @date 2022/12/22 * @description 死鎖測試 */public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new MyThread(lockA, lockB), "T1").start(); new Thread(new MyThread(lockB, lockA), "T2").start(); }}class MyThread implements Runnable { private String lockA; private String lockB; public MyThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "lock:" + lockA + "=> get" + lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "lock:" + lockB + "=> get" + lockA); } } }}
解決問題
1、使用 ??jps -l?
? 定位進程號
2、使用 ??jstack 進程號?
?找到死鎖問題