來源:blog.csdn.net/wangwenpeng0529/article/details/105769978
簡介
在 Java 5.0 提供了 java.util.concurrent(簡稱JUC)包,在此包中增加了在并發編程中很常用的工具類,用于定義類似于執行緒的自定義子系統,包括執行緒池,異步 IO 和輕量級任務框架;還提供了設計用于多執行緒背景關系中的 Collection 實作等
volatile 關鍵字
記憶體可見性
記憶體可見性(Memory Visibility)是指當某個執行緒正在使用物件狀態而另一個執行緒在同時修改該狀態,需要確保當一個執行緒修改了物件狀態后,其他執行緒能夠看到發生的狀態變化,
可見性錯誤是指當讀操作與寫操作在不同的執行緒中執行時,我們無法確保執行讀操作的執行緒能適時地看到其他執行緒寫入的值,有時甚至是根本不可能的事情,
我們可以通過同步來保證物件被安全地發布,除此之外我們也可以使用一種更加輕量級的 volatile 變數,
Java 提供了一種稍弱的同步機制,即 volatile 變數,用來確保將變數的更新操作通知到其他執行緒,可以將 volatile 看做一個輕量級的鎖,但是又與鎖有些不同:
- 對于多執行緒,不是一種互斥關系
- 不能保證變數狀態的“原子性操作
問題代碼示例
/**
* @ClassName TestVolatile
* @Description: Thread 已經修改了flag,但是main執行緒還是拿到的false
* @Author: WangWenpeng
* @Version 1.0
*/
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
if (td.isFlag()) {
System.out.println("______________");
break;
}
}
}
}
class ThreadDemo implements Runnable {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
//增加這種出現問題的幾率
Thread.sleep(200);
} catch (Exception e) {
}
flag = true;
System.out.println("flag=" + isFlag());
}
}

兩個執行緒同時修改這一個flag,為什么main拿到的還是這種修改之前的值
記憶體分析

解決方法,加鎖
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true) {
synchronized (td) {
if (td.isFlag()) {
System.out.println("______________");
break;
}
}
}
}
}

加了鎖,就可以讓while回圈每次都從主存中去讀取資料,這樣就能讀取到true了,但是一加鎖,每次只能有一個執行緒訪問,當一個執行緒持有鎖時,其他的就會阻塞,效率就非常低了,不想加鎖,又要解決記憶體可見性問題,那么就可以使用volatile關鍵字,
volatile
private volatile boolean flag = false;
volatile 關鍵字:當多個執行緒進行操作共享資料時,可以保證記憶體中的資料可見,相較于 synchronized 是一種較為輕量級的同步策略,
注意:
- volatile 不具備“互斥性”
- volatile 不能保證變數的“原子性”
原子性
所謂原子性就是操作不可再細分
問題代碼
package com.atguigu.juc;
/**
* @ClassName TestAtomicDemo
* @Description:
* @Author: WangWenpeng
* @Version 1.0
*/
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable {
private int serialNumber = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
}
public int getSerialNumber() {
return serialNumber++;
}
}


看到這里,好像和上面的記憶體可見性問題一樣,是不是加個volatile關鍵字就可以了呢?其實不是的,因為加了volatile,只是相當于所有執行緒都是在主存中操作資料而已,但是不具備互斥性,比如兩個執行緒同時讀取主存中的0,然后又同時自增,同時寫入主存,結果還是會出現重復資料,
import java.util.concurrent.atomic.AtomicInteger;
/**
* @ClassName TestAtomicDemo
*
* 原子變數:在 java.util.concurrent.atomic 包下提供了一些原子變數,
* 1. volatile 保證記憶體可見性
* 2. CAS(Compare-And-Swap) 演算法保證資料變數的原子性
* CAS 演算法是硬體對于并發操作的支持
* CAS 包含了三個運算元:
* ①記憶體值 V
* ②預估值 A
* ③更新值 B
* 當且僅當 V == A 時, V = B; 否則,不會執行任何操作,
*
* @Description:
* @Author: WangWenpeng
* @Version 1.0
*/
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable {
private AtomicInteger serialNumber = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
}
public int getSerialNumber() {
return serialNumber.getAndIncrement();
}
}
AtomicInteger這個玩意是具有原子性的integer,用它替換后發現能保證執行緒安全
Connected to the target VM, address: '127.0.0.1:61323', transport: 'socket'
Thread-4:1
Thread-6:4
Thread-0:3
Thread-7:9
Thread-2:2
Thread-5:6
Thread-3:5
Thread-1:0
Thread-9:7
Thread-8:8
Disconnected from the target VM, address: '127.0.0.1:61323', transport: 'socket'
CAS 演算法
解決了原子性問題,解決了記憶體可見性的問題
CAS (Compare-And-Swap) 是一種硬體對并發的支持,針對多處理器操作而設計的處理器中的一種特殊指令,用于管理對共享資料的并發訪問,CAS 是一種無鎖的非阻塞演算法的實作,
CAS 包含了 3 個運算元:
- 需要讀寫的記憶體值 V 進行比較的值 A 擬寫入的新值 B
- 當且僅當 V 的值等于 A 時, CAS 通過原子方式用新值 B 來更新 V 的值,否則不會執行任何操作,
- CAS比較失敗的時候不會放棄CPU,會反復執行,直到自己修改主記憶體的資料
模擬CAS演算法
/**
* @ClassName TestCompareAndSwap
* @Description: cas模擬 模擬帶鎖,底層不是帶synchronized
* cas 每次修改之前,都會執行獲取比較操作
* @Author: WangWenpeng
* @Version 1.0
*/
public class TestCompareAndSwap {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectValue = https://www.cnblogs.com/javastack/p/cas.getValue();
System.out.println(cas.compareAndSet(expectValue, (int) Math.random() * 101));
}
}).start();
}
}
}
class CompareAndSwap {
public int value;
//獲取記憶體值
public synchronized int getValue() {
return value;
}
//比較并交換
public synchronized int compareAndSwap(int expectValue, int newV) {
int oldV = value;
//記憶體值和預估值一致 就替換
if (oldV == expectValue) {
this.value = newV;
}
return oldV;
}
//設定 呼叫比較并交換 看期望值和原來的值是否一致
public synchronized boolean compareAndSet(int expectValue, int newV) {
return expectValue == compareAndSwap(expectValue, newV);
}
}
推薦一個 Spring Boot 基礎教程及實戰示例:
https://github.com/javastacks/javastack
原子變數
小工具包,支持在單個變數上解除鎖的執行緒安全編程,事實上,此包中的類可將 volatile 值、欄位和陣列元素的概念擴展到那些也提供原子條件更新操作的類,
類 AtomicBoolean、 AtomicInteger、 AtomicLong 和 AtomicReference 的實體各自提供對相應型別單個變數的訪問和更新,每個類也為該型別提供適當的實用工具方法,
AtomicIntegerArray、 AtomicLongArray 和 AtomicReferenceArray 類進一步擴展了原子操作,對這些型別的陣列提供了支持,這些類在為其陣列元素提供 volatile 訪問語意方面也引人注目,這對于普通陣列來說是不受支持的,
核心方法:boolean compareAndSet(expectedValue, updateValue)
java.util.concurrent.atomic 包下提供了一些原子操作的常用類:
AtomicBoolean 、
AtomicInteger 、
AtomicLong 、
AtomicReference
AtomicIntegerArray 、
AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
AtomicStampedReference3-ConcurrentHashMap
鎖分段機制ConcurrentHashMap
執行緒安全的hash表 每一段都是一個獨立的鎖
Java 5.0 在 java.util.concurrent 包中提供了多種并發容器類來改進同步容器的性能,
ConcurrentHashMap 同步容器類是Java 5 增加的一個執行緒安全的哈希表,對與多執行緒的操作,介于 HashMap 與 Hashtable 之間,內部采用“鎖分段”機制替代 Hashtable 的獨占鎖,進而提高性能,
此包還提供了設計用于多執行緒背景關系中的 Collection 實作:ConcurrentHashMap、 ConcurrentSkipListMap、 ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet,當期望許多執行緒訪問一個給定 collection 時, ConcurrentHashMap 通常優于同步的 HashMap,ConcurrentSkipListMap 通常優于同步的 TreeMap,當期望的讀數和遍歷遠遠大于串列的更新數時, CopyOnWriteArrayList 優于同步的 ArrayList,
ConcurrentHashMap就是一個執行緒安全的hash表,我們知道HashMap是執行緒不安全的,Hash Table加了鎖,是執行緒安全的,因此它效率低,HashTable加鎖就是將整個hash表鎖起來,當有多個執行緒訪問時,同一時間只能有一個執行緒訪問,并行變成串行,因此效率低,所以JDK1.5后提供了ConcurrentHashMap,它采用了鎖分段機制,

1.8以后底層又換成了CAS,把鎖分段機制放棄了,CAS基本就達到了無鎖的境界,
另外,Java 8+ 系列面試題和答案全部整理好了,微信搜索?Java技術堆疊,在后臺發送:面試,?可以在線閱讀,
CopyOnWrite寫入并復制
package com.atguigu.juc;import java.util.*;import java.util.concurrent.CopyOnWriteArrayList;/* * CopyOnWriteArrayList/CopyOnWriteArraySet : “寫入并復制” * 注意:添加操作多時,效率低,因為每次添加時都會進行復制,開銷非常的大,并發迭代操作多時可以選擇, */public class TestCopyOnWriteArrayList { public static void main(String[] args) { HelloThread ht = new HelloThread(); for (int i = 0; i < 10; i++) { new Thread(ht).start(); } }}class HelloThread implements Runnable { //private static List<String> list = Collections.synchronizedList(new ArrayList<String>()); //每次修改都會復制 添加操作多時 不適合選這個 private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); static { list.add("AA"); list.add("BB"); list.add("CC"); } @Override public void run() { Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); list.add("AA");//邊迭代邊添加 會出現并發修改例外 } }}
CountDownLatch 閉鎖
閉鎖,在完成某些運算時,只有其他所有執行緒的運算全部完成,當前運算才繼續執行,Java 5.0 在 java.util.concurrent 包中提供了多種并發容器類來改進同步容器的性能,
CountDownLatch 一個同步輔助類,在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待,
閉鎖可以延遲執行緒的進度直到其到達終止狀態,閉鎖可以用來確保某些活動直到其他活動都完成才繼續執行:
- 確保某個計算在其需要的所有資源都被初始化之后才繼續執行;
- 確保某個服務在其依賴的所有其他服務都已經啟動之后才啟動;
- 等待直到某個操作所有參與者都準備就緒再繼續執行,
import java.util.concurrent.CountDownLatch;/** * @ClassName TestCountDownLatch * @Description: 閉鎖操作 其他執行緒都執行完成后當前執行緒才能繼續執行 * @Author: WangWenpeng * @Version 1.0 */public class TestCountDownLatch { public static void main(String[] args) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(5); LatchDemo ld = new LatchDemo(latch); //計算執行時間 long start = System.currentTimeMillis(); for (int i = 0; i < 5; i++) { new Thread(ld).start(); } //閉鎖 等待其他執行緒的執行 latch.await(); long end = System.currentTimeMillis(); System.out.println("執行時間===============================" + (end - start)); }}class LatchDemo implements Runnable { private CountDownLatch latch; public LatchDemo(CountDownLatch latch) { this.latch = latch; } @Override public void run() { synchronized (this) { try { for (int i = 0; i < 1000; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + "-------------" + i); } } } finally { //執行緒執行完畢后 countdown 減一 latch.countDown(); } } }}
實作 Callable 介面
Java 5.0 在 java.util.concurrent 提供了一個新的創建執行執行緒的方式:Callable 介面
Callable 介面類似于 Runnable,兩者都是為那些其實體可能被另一個執行緒執行的類設計的,但是 Runnable 不會回傳結果,并且無法拋出經過檢查的例外,
Callable 需要依賴FutureTask , FutureTask 也可以用作閉鎖,
package com.atguigu.juc;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** * @ClassName TestCallable * @Description: * @Author: WangWenpeng * @Version 1.0 */public class TestCallable { public static void main(String[] args) throws ExecutionException, InterruptedException { CallableThreadDemo td = new CallableThreadDemo(); //futureTask 實作類的支持 用于接收運算結果 FutureTask<Integer> result = new FutureTask<>(td); new Thread(result).start();//執行緒開始運行 Integer sum = result.get();//等待執行緒執行完成后 才能獲取到結果 也可以用于閉鎖操作作為等待項 System.out.println("總和" + sum); }}/** * @Description 多了一個方法的回傳值 并且可以拋出例外 * @Author WangWenpeng * @Param */class CallableThreadDemo implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; }}//class ThreadDemo implements Runnable{// @Override// public void run() {//// }//}
同步鎖顯示鎖 Lock
在 Java 5.0 之前,協調共享物件的訪問時可以使用的機制只有 synchronized 和 volatile ,Java 5.0 后增加了一些新的機制,但并不是一種替代內置鎖的方法,而是當內置鎖不適用時,作為一種可選擇的高級功能,
ReentrantLock 實作了 Lock 介面,并提供了與synchronized 相同的互斥性和記憶體可見性,但相較于synchronized 提供了更高的處理鎖的靈活性,
package com.atguigu.juc;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @ClassName TestLock * @Description: 同步鎖 更靈活的方式 * lock上鎖 unlock釋放鎖 * @Author: WangWenpeng * @Version 1.0 */public class TestLock { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(ticket, "1號視窗").start(); new Thread(ticket, "2號視窗").start(); new Thread(ticket, "3號視窗").start(); }}class Ticket implements Runnable { private int ticket = 100; private Lock lock = new ReentrantLock(); @Override public void run() { //這樣買票沒有問題 //while (ticket > 0) { // System.out.println(Thread.currentThread().getName() + "完成售票,余票為" + --ticket); //} //放大問題出現的記錄 出現了負票號 //while (true) { // if (ticket > 0) { // try { // Thread.sleep(200); // System.out.println(Thread.currentThread().getName() + "完成售票,余票為" + --ticket); // } catch (InterruptedException e) { // e.printStackTrace(); // } // } //} //顯式加鎖和釋放鎖 while (true) { lock.lock(); try { if (ticket > 0) { try { Thread.sleep(200); System.out.println(Thread.currentThread().getName() + "完成售票,余票為" + --ticket); } catch (InterruptedException e) { e.printStackTrace(); } } } finally { lock.unlock(); } } }}
lock的等待喚醒機制
/**
* @ClassName TestProductorAndConsumer
* @Description: 生產者消費者模型
* @Author: WangWenpeng
* @Version 1.0
*/
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
//沒有等待喚醒機制的時候
//生產者一直生產 不考錄消費者 可能造成資料丟失
//消費者一直消費 不考慮生產者 可能造成重復消費
new Thread(productor, "生產者a").start();
new Thread(consumer, "消費者a").start();
}
}
/**
* 店員
*/
class Clerk {
//庫存共享資料 存在安全問題
private int product = 0;
//進貨
public synchronized void get() {
if (product >= 10) {
System.out.println("產品已滿,無法添加");
try {
this.wait();
} catch (InterruptedException e) {
}
} else {
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product);
}
}
//賣貨
public synchronized void sale() {
if (product <= 0) {
System.out.println("產品缺貨,無法售賣");
try {
this.wait();
} catch (InterruptedException e) {
}
} else {
System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product);
this.notifyAll();
}
}
}
/**
* 生產者
*/
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
/**
* @Description 消費者
* @Author WangWenpeng
* @Date 6:45 2020/4/27
* @Param
*/
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
lock出問題的情況
生產者等待,增加出問題的幾率 庫存空位改成1
if (product >= 1) {
System.out.println("產品已滿,無法添加");
--------------------------------------------------------------------------------------
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.get();
}
}

消費者等于0的時候, 兩個消費者同時生產,之后停住了,沒有其他執行緒去喚醒,導致停在生產者這里,
解決方法,去掉else,讓他能走喚醒方法,
//進貨public synchronized void get() { if (product >= 1) { System.out.println("產品已滿,無法添加"); try { this.wait(); } catch (InterruptedException e) { } } this.notifyAll(); System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product);}//賣貨public synchronized void sale() { if (product <= 0) { System.out.println("產品缺貨,無法售賣"); try { this.wait(); } catch (InterruptedException e) { } } System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product); this.notifyAll();}
讓執行緒能走到notifyall,可以避免停止在生產者這里,
虛假喚醒
增加到兩個消費者兩個生產者之后,如果現在沒有庫存,兩個消費者都停止在wait,然后出現生產者將庫存加一,喚醒所有消費者,這時候就出現了兩個消費者同時去消費一個庫存,導致庫存變成負數,這就是虛假喚醒,

在object類的wait方法中,虛假喚醒是可能的,因此這個wait方法應該總被使用在回圈中
解決方法
將代碼中的if換為while回圈執行
//進貨 public synchronized void get() { while (product >= 1) { //wait使用在回圈中 System.out.println("產品已滿,無法添加"); try { this.wait(); } catch (InterruptedException e) { } } this.notifyAll(); System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product); } //賣貨 public synchronized void sale() { while (product <= 0) { System.out.println("產品缺貨,無法售賣"); try { this.wait(); } catch (InterruptedException e) { } } System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product); this.notifyAll(); }
控制執行緒通信Condition
Condition 介面描述了可能會與鎖有關聯的條件變數,這些變數在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能,需要特別指出的是,單個 Lock 可能與多個 Condition 物件關聯,為了避免兼容性問題, Condition 方法的名稱與對應的 Object 版本中的不同,
在 Condition 物件中,與 wait、 notify 和 notifyAll 方法對應的分別是await、 signal 和 signalAll,Condition 實體實質上被系結到一個鎖上,要為特定 Lock 實體獲得Condition 實體,請使用其 newCondition()方法,
/** * 店員 */class ClerkLock { //庫存共享資料 存在安全問題 private int product = 0; //使用lock,去掉synchronized this.wait和lock就是兩把鎖,用lock統一 private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //進貨 public void get() { lock.lock(); try { while (product >= 1) { System.out.println("產品已滿,無法添加"); try { condition.await(); } catch (InterruptedException e) { } } condition.signalAll(); System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product); } finally { lock.unlock(); } } //賣貨 public synchronized void sale() { lock.lock(); try { while (product <= 0) { System.out.println("產品缺貨,無法售賣"); try { condition.await(); } catch (InterruptedException e) { } } System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product); condition.signalAll(); } finally { } }}
這里店員的代碼全部處理為condition,用他的方法實作執行緒的通信,
執行緒按序交替執行緒按序交替
撰寫一個程式,開啟 3 個執行緒,這三個執行緒的 ID 分別為A、 B、 C,每個執行緒將自己的 ID 在螢屏上列印 10 遍,要求輸出的結果必須按順序顯示,
如:ABCABCABC…… 依次遞回9-ReadWriteLock 讀寫鎖讀-寫鎖 ReadWriteLock
package com.atguigu.juc;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @ClassName TestABCAlternate * @Description: 執行緒交替列印 * @Author: WangWenpeng * @Version 1.0 */public class TestABCAlternate { public static void main(String[] args) { Alternate alternate = new Alternate(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <= 20; i++) { alternate.loopA(i); } } }, "A").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <= 20; i++) { alternate.loopB(i); } } }, "B").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <= 20; i++) { alternate.loopC(i); } } }, "C").start(); }}class Alternate { private int number = 1;//當前正在執行的執行緒號 private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); public void loopA(int totalLoop) { lock.lock(); try { //1.判斷1號執行緒 if (number != 1) { condition1.await(); } //2.開始列印 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } //3.喚醒執行緒2 number = 2; condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void loopB(int totalLoop) { lock.lock(); try { //1.判斷1號執行緒 if (number != 2) { condition2.await(); } //2.開始列印 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } //3.喚醒執行緒2 number = 3; condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void loopC(int totalLoop) { lock.lock(); try { //1.判斷1號執行緒 if (number != 3) { condition3.await(); } //2.開始列印 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } //3.喚醒執行緒2 number = 1; condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }}
ReadWriteLock 讀寫鎖
ReadWriteLock 維護了一對相關的鎖,一個用于只讀操作,另一個用于寫入操作,只要沒有 writer,讀取鎖可以由多個 reader 執行緒同時保持,寫入鎖是獨占的,
ReadWriteLock 讀取操作通常不會改變共享資源,但執行寫入操作時,必須獨占方式來獲取鎖,對于讀取操作占多數的資料結構,ReadWriteLock 能提供比獨占鎖更高的并發性,而對于只讀的資料結構,其中包含的不變性可以完全不需要考慮加鎖操作,

讀鎖是多個執行緒可以一起,寫鎖是獨占的
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @ClassName ReadWriteLock
* @Description: 讀寫鎖 讀和寫之間不互斥 寫和寫之間互斥
* @Author: WangWenpeng
* @Version 1.0
*/
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
demo.set((int) (Math.random() * 101));
}
}, "writeLock").start();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
demo.get();
}
}, "readLock-" + i).start();
}
}
}
class ReadWriteLockDemo {
private int number = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
//讀
public void get() {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "讀:" + number);
} finally {
lock.readLock().unlock();
}
}
//寫
public void set(int number) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "寫:" + number);
this.number = number;
} finally {
lock.writeLock().unlock();
}
}
}
執行緒八鎖
- 一個物件里面如果有多個synchronized方法,某一個時刻內,只要一個執行緒去呼叫其中的一個synchronized方法了,其它的執行緒都只能等待,換句話說,某一個時刻內,只能有唯一一個執行緒去訪問這些synchronized方法
- 鎖的是當前物件this,被鎖定后,其它的執行緒都不能進入到當前物件的其它的synchronized方法
- 加個普通方法后發現和同步鎖無關
- 換成兩個物件后,不是同一把鎖了,情況立刻變化,
- 都換成靜態同步方法后,情況又變化
- 所有的非靜態同步方法用的都是同一把鎖——實體物件本身,也就是說如果一個實體物件的非靜態同步方法獲取鎖后,該實體物件的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,可是別的實體物件的非靜態同步方法因為跟該實體物件的非靜態同步方法用的是不同的鎖,所以毋須等待該實體物件已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖,
- 所有的靜態同步方法用的也是同一把鎖——類物件本身,這兩把鎖是兩個不同的物件,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的,但是一旦一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個實體物件的靜態同步方法之間,還是不同的實體物件的靜態同步方法之間,只要它們同一個類的實體物件!
/**
* 1. 兩個普通同步方法,兩個執行緒,標準列印, 列印? //one two
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number {
public synchronized void getOne() {
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
}
/**
2. 新增 Thread.sleep() 給 getOne() ,列印? //one two
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number {
public synchronized void getOne() {
try {
Thread.sleep(3000);//讓one 睡3秒
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
}
/*
*3. 新增普通方法 getThree() , 列印? //three one two
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();
}
}
class Number {
public synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
//普通方法
public void getThree(){
System.out.println("three");
}
}
/*
* 4. 兩個普通同步方法,兩個 Number 物件,列印? //two one
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number2.getTwo();
}
}).start();
}
}
class Number {
public synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
}
/*
* 5. 修改 getOne() 為靜態同步方法,列印? //two one
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}//這樣其實不能通過類的實體訪問靜態,為演示這個問題
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number {
//靜態同步方法
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
}
/*
* 6. 修改兩個方法均為靜態同步方法,一個 Number 物件? //one two
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}//這樣其實不能通過類的實體訪問靜態,為演示這個問題
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number {
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public static synchronized void getTwo() {
System.out.println("two");
}
}
/*
* 7. 一個靜態同步方法,一個非靜態同步方法,兩個 Number 物件? //two one
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}//這樣其實不能通過類的實體訪問靜態,為演示這個問題
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number2.getTwo();
}
}).start();
}
}
class Number {
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
}
/*
* 8. 兩個靜態同步方法,兩個 Number 物件? //one two
*/
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}//這樣其實不能通過類的實體訪問靜態,為演示這個問題
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number2.getTwo();
}
}).start();
}
}
class Number {
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public static synchronized void getTwo() {
System.out.println("two");
}
}
執行緒八鎖的關鍵:
- 非靜態方法的鎖默認為 this, 靜態方法的鎖為 對應的 Class 實體
- 某一個時刻內,只能有一個執行緒持有鎖,無論幾個方法,
執行緒池
第四種獲取執行緒的方法:執行緒池,一個 ExecutorService,它使用可能的幾個池執行緒之一執行每個提交的任務,通常使用 Executors 工廠方法配置,
執行緒池可以解決兩個不同問題:由于減少了每個任務呼叫的開銷,它們通常可以在執行大量異步任務時提供增強的性能,并且還可以提供系結和管理資源(包括執行任務集時使用的執行緒)的方法,每個 ThreadPoolExecutor 還維護著一些基本的統計資料,如完成的任務數,
為了便于跨大量背景關系使用,此類提供了很多可調整的引數和擴展鉤子 (hook),但是,強烈建議程式員使用較為方便的 Executors 工廠方法 :
Executors.newCachedThreadPool()(無界執行緒池,可以進行自動執行緒回收)Executors.newFixedThreadPool(int)(固定大小執行緒池)Executors.newSingleThreadExecutor()(單個后臺執行緒)它們均為大多數使用場景預定義了設定,
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @Description 一、執行緒池:提供了一個執行緒佇列,佇列中保存著所有等待狀態的執行緒,避免了創建與銷毀額外開銷,提高了回應的速度,
* 二、執行緒池的體系結構:
* java.util.concurrent.Executor : 負責執行緒的使用與調度的根介面
* |--**ExecutorService 子介面: 執行緒池的主要介面
* |--ThreadPoolExecutor 執行緒池的實作類
* |--ScheduledExecutorService 子介面:負責執行緒的調度
* |--ScheduledThreadPoolExecutor :繼承 ThreadPoolExecutor, 實作 ScheduledExecutorService
* 三、工具類 : Executors
* ExecutorService newFixedThreadPool() : 創建固定大小的執行緒池
* ExecutorService newCachedThreadPool() : 快取執行緒池,執行緒池的數量不固定,可以根據需求自動的更改數量,
* ExecutorService newSingleThreadExecutor() : 創建單個執行緒池,執行緒池中只有一個執行緒
* ScheduledExecutorService newScheduledThreadPool() : 創建固定大小的執行緒,可以延遲或定時的執行任務,
* @Author WangWenpeng
* @Param
*/
public class TestThreadPool {
public static void main(String[] args) throws Exception {
//1. 創建執行緒池
ExecutorService pool = Executors.newFixedThreadPool(5);
//submit Callable方法
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
});
list.add(future);
}
pool.shutdown();
for (Future<Integer> future : list) {
System.out.println(future.get());
}
//submit Runnable方法
ThreadPoolDemo tpd = new ThreadPoolDemo();
//2. 為執行緒池中的執行緒分配任務
for (int i = 0; i < 10; i++) {
pool.submit(tpd);
}
//3. 關閉執行緒池
pool.shutdown();
}
}
class ThreadPoolDemo implements Runnable {
private int i = 0;
@Override
public void run() {
while (i <= 100) {
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
執行緒調度ScheduledExecutorService
一個 ExecutorService,可安排在給定的延遲后運行或定期執行的命令,
public static void main(String[] args) throws Exception {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
Future<Integer> result = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(100);//生成亂數
System.out.println(Thread.currentThread().getName() + " : " + num);
return num;
}
}, 1, TimeUnit.SECONDS);
System.out.println(result.get());
}
pool.shutdown();
}
ForkJoinPool 分支/合并框架
就是在必要的情況下,將一個大任務,進行拆分(fork)成若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進行 join 匯總,
JoinFork/Join 框架與執行緒池的區別
采用 “作業竊取”模式(work-stealing):
- 當執行新的任務時它可以將其拆分分成更小的任務執行,并將小任務加到執行緒佇列中,然后再從一個隨機執行緒的佇列中偷一個并把它放在自己的佇列中,
- 相對于一般的執行緒池實作, fork/join框架的優勢體現在對其中包含的任務的處理方式上,在一般的執行緒池中, 如果一個執行緒正在執行的任務由于某些原因無法繼續運行, 那么該執行緒會處于等待狀態,而在fork/join框架實作中,如果某個子問題由于等待另外一個子問題的完成而無法繼續運行,那么處理該子問題的執行緒會主動尋找其他尚未運行的子問題來執行.這種方式減少了執行緒的等待時間, 提高了性能,
public class TestForkJoinPool {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 50000000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗費時間為:" + Duration.between(start, end).toMillis());//166-1996-10590
}
}
class ForkJoinSumCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID = -259195479995561737L;
private long start;
private long end;
private static final long THURSHOLD = 10000L; //臨界值
public ForkJoinSumCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THURSHOLD) {
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
long middle = (start + end) / 2;
ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
left.fork(); //進行拆分,同時壓入執行緒佇列
ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/337874.html
標籤:Java
