1.JUC
JUC:java.util.concurrent
- 在并發(fā)編程中使用的工具類
- java.util.concurrent 并發(fā)包
- java.util.concurrent.atomic 并發(fā)原子包
- java.util.concurrent.locks 并發(fā)lock包
2.多線程編程
-
模板
- 線程 操作 資源類(資源類不實現(xiàn)Runnable接口)
- 操作:資源對外暴露的調用方法
- 高內聚低耦合
- 線程 操作 資源類(資源類不實現(xiàn)Runnable接口)
-
步驟
- 創(chuàng)建資源類
- 資源類里創(chuàng)建同步方法、同步代碼塊
-
synchronized
class X { public synchronized void m() { //todo } }
/**
* 線程 操作 資源類
*/
public class SaleTicket {
public static void main(String[] args) {
//資源
Ticket ticket = new Ticket();
//線程
new Thread(() -> { for (int i = 0; i < 100; i++) ticket.sellTicket(); }, "A").start();
new Thread(() -> { for (int i = 0; i < 100; i++) ticket.sellTicket(); }, "B").start();
new Thread(() -> { for (int i = 0; i < 100; i++) ticket.sellTicket(); }, "C").start();
}
}
//資源類
class Ticket {
private Integer number = 100;
//操作:對外暴露操作資源的方法
public synchronized void sellTicket() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "\t 賣出第" + number-- + "張票,還剩"
+ number + "張");
}
}
}
3.Lock
3.1 Lock
-
java.util.concurrent.locks.Lock
Lock
implementations provide more extensive locking operations than can be obtained usingsynchronized
methods and statements. They allow more flexible structuring, may have quite different properties, and may support multiple associatedCondition
objects.鎖實現(xiàn)提供了比使用同步方法和同步代碼塊更廣泛的鎖操作。它們允許更靈活的結構,可能具有完全不同的屬性,并且可能支持多個關聯(lián)的條件對象。
3.2 ReentrantLock可重入鎖
可重入鎖:某個線程已經獲得某個鎖,可以再次獲取鎖而不會出現(xiàn)死鎖,獲取和釋放的次數(shù)需要一致
Lock接口的實現(xiàn)類,java.util.concurrent.locks.ReentrantLock
-
使用
class X { private final Lock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }
-
構造方法
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync();//非公平鎖 } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) {//FairSync 公平鎖 sync = fair ? new FairSync() : new NonfairSync(); }
公平鎖:十分公平,按先來后到順序
非公平鎖:十分不公平,可以插隊(默認,如線程A在前,需要1小時,線程B在后,需要3秒,非公平鎖允許線程B插隊,先完成,而不需要等待線程A完成后再執(zhí)行)
3.3 synchronized&&Lock的區(qū)別
- synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
- synchronized無法判斷是否獲取鎖的狀態(tài),Lock可以判斷是否獲取到鎖;
- synchronized會自動釋放鎖(線程執(zhí)行完同步代碼會釋放鎖、線程執(zhí)行過程中發(fā)生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
- 用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
- synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(公平非公平兩者皆可)
- Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
4. 線程間的通信
-
線程間通信
- 生產者+消費者
- 通知等待喚醒機制
-
多線程編程模板
- 判斷 干活 通知
- 判斷需使用while,以防止中斷和虛假喚醒(見java.lang.Object的API)
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one
synchronized (obj) { while (<condition does not hold>) obj.wait(timeout); ... // Perform action appropriate to condition }
線程也可以在沒有通知、中斷或超時的情況下被喚醒,這就是所謂的假喚醒。雖然這種情況在實踐中很少發(fā)生,但應用程序必須通過測試導致線程被喚醒的條件來防止這種情況發(fā)生,如果條件不滿足,則繼續(xù)等待。換句話說,等待應該總是出現(xiàn)在循環(huán)中,就像這個循環(huán)一樣
4.1 synchronized版
-
synchronized實現(xiàn)
- 先2個線程操作資源類,資源中的操作判斷使用if,如線程A和線程B,可以正常運行1 0 1 0 1 0...
- 增加2個線程C和D,模擬虛假喚醒,判斷依舊是if,運行的結果數(shù)字不是全部為1、0
- 原因:在java多線程判斷時,不能用if,程序出事出在了判斷上面,突然有一添加的線程進到if了,突然中斷了交出控制權,沒有進行驗證,而是直接走下去了,加了兩次,甚至多次
- 在4線程中,將資源類的if判斷改為while判斷,while是只要喚醒就要拉回來再判斷一次
package juc.demo; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Description: * 現(xiàn)在兩個線程, * 可以操作初始值為零的一個變量, * 實現(xiàn)一個線程對該變量加1,一個線程對該變量減1, * 交替,來10輪。 * @Package: juc.demo * @ClassName NotifyWaitDemo * @author: wuwb * @date: 2020/10/19 13:30 */ public class NotifyWaitDemo { public static void main(String[] args) { int turn = 1000; //資源類 ShareData data = new ShareData(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.increment(); } catch (Exception e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.decrement(); } catch (Exception e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.increment(); } catch (Exception e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.decrement(); } catch (Exception e) { e.printStackTrace(); } } }, "D").start(); } } //資源類 class ShareData{ private int number = 0; public synchronized void increment() throws InterruptedException { //判斷 if換while while (number != 0) { this.wait(); } //干活 number++; System.out.println(Thread.currentThread().getName() + ":" + number); //通知 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { while (number == 0) { this.wait(); } number--; System.out.println(Thread.currentThread().getName() + ":" + number); this.notifyAll(); } }
4.2 JUC版
-
Lock 及 Condition
package juc.demo; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Description: * 現(xiàn)在兩個線程, * 可以操作初始值為零的一個變量, * 實現(xiàn)一個線程對該變量加1,一個線程對該變量減1, * 交替,來10輪。 * @Package: juc.demo * @ClassName NotifyWaitDemo * @author: wuwb * @date: 2020/10/19 13:30 */ public class NotifyWaitDemo { public static void main(String[] args) { int turn = 1000; //資源類 ShareData data = new ShareData(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.increment(); } catch (Exception e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.decrement(); } catch (Exception e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.increment(); } catch (Exception e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 0; i < turn; i++) { try { data.decrement(); } catch (Exception e) { e.printStackTrace(); } } }, "D").start(); } } //資源類 class ShareData{ private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() { lock.lock(); try { //判斷 while (number != 0) { condition.await();//this.wait(); } //干活 number++; System.out.println(Thread.currentThread().getName() + ":" + number); //通知 condition.signalAll();//this.notifyAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() { lock.lock(); try { while (number == 0) { condition.await(); } number--; System.out.println(Thread.currentThread().getName() + ":" + number); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
4.3 定制化調用通信
- 使用Lock、Condition指定調用順序,指定喚醒哪個線程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Description
* 多線程之間按順序調用,實現(xiàn)A->B->C
* ......來10輪
*/
public class ThreadOrderAccess {
public static void main(String[] args) {
ShareResource resource = new ShareResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printC();
}
}, "C").start();
}
}
class ShareResource{
/**標志位*/
private int number = 1;
private Lock lock = new ReentrantLock();
/**3把鑰匙*/
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"==>AAAAAAAAAA");
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"==>BBBBBBBBBB");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"==>CCCCCCCCCC");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5. 多線程鎖
5.1 八鎖現(xiàn)象
- 問題一:標準訪問,先打印短信還是郵件?
public class Lock_8_1 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> { phone.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone.sendEmail(); }, "BB").start();
}
}
class Phone {
public synchronized void sendSMS() {
System.out.println("------sendSMS");
}
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
}/*sendSMS*/
- 問題二:停4秒在短信方法內,先打印短信還是郵件?
public class Lock_8_2 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> { phone.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone.sendEmail(); }, "BB").start();
}
}
class Phone {
public synchronized void sendSMS() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
}/*sendSMS*/
- 問題三:新增普通的getHello方法,是先打印短信還是hello?
public class Lock_8_3 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> { phone.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone.getHello(); }, "BB").start();
}
}
class Phone {
public synchronized void sendSMS() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}/*getHello*/
- 問題四:現(xiàn)在有兩部手機,先打印短信還是郵件?
public class Lock_8_4 {
public static void main(String[] args) throws Exception {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> { phone1.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone2.sendEmail(); }, "BB").start();
}
}
class Phone {
public synchronized void sendSMS() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
}/*sendEmail*/
- 問題五:兩個靜態(tài)同步方法,1部手機,先打印短信還是郵件?
public class Lock_8_5 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> { phone.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone.sendEmail(); }, "BB").start();
}
}
class Phone {
public static synchronized void sendSMS() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public static synchronized void sendEmail() {
System.out.println("------sendEmail");
}
}/*sendSMS*/
- 問題六:兩個靜態(tài)同步方法,2部手機,先打印短信還是郵件?
public class Lock_8_6 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> { phone.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone2.sendEmail(); }, "BB").start();
}
}
class Phone {
public static synchronized void sendSMS() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public static synchronized void sendEmail() {
System.out.println("------sendEmail");
}
}/*sendSMS*/
- 問題七:1個靜態(tài)同步方法,1個普通同步方法,1部手機,先打印短信還是郵件?
public class Lock_8_7 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
new Thread(() -> { phone.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone.getHello(); }, "BB").start();
}
}
class Phone {
public static synchronized void sendSMS() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
}/*sendEmail*/
- 問題八:1個靜態(tài)同步方法,1個普通同步方法,2部手機,先打印短信還是郵件
public class Lock_8_8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> { phone.sendSMS(); }, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> { phone2.sendEmail(); }, "BB").start();
}
}
class Phone {
public static synchronized void sendSMS() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
}/*sendEmail*/
5.2 多線程鎖
- synchronized實現(xiàn)同步的基礎:Java中的每一個對象都可以作為鎖。具體表現(xiàn)為以下3種形式。
- 對于普通同步方法,鎖是當前實例對象。
- 對于靜態(tài)同步方法,鎖是當前類的Class對象。
- 對于同步方法塊,鎖是Synchonized括號里配置的對象
- 當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。
- 如果一個實例對象的非靜態(tài)同步方法獲取鎖后,該實例對象的其他非靜態(tài)同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖
- 一個對象里面如果有多個synchronized方法,某一個時刻內,只要一個線程去調用其中的一個synchronized方法了,其它的線程都只能等待,換句話說,某一個時刻內,只能有唯一一個線程去訪問這些synchronized方法,鎖的是當前對象this,被鎖定后,其它的線程都不能進入到當前對象的其它的synchronized方法(問題一、問題二)
- 不同的實例對象的非靜態(tài)同步方法因為鎖的是各自的實例對象,所以互不影響(問題四)
- 加個普通方法后發(fā)現(xiàn)和同步鎖無關(問題三)
- 所有的靜態(tài)同步方法用的也是同一把鎖——類對象本身,一旦一個靜態(tài)同步方法獲取鎖后,其他的靜態(tài)同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個實例對象的靜態(tài)同步方法之間,還是不同的實例對象的靜態(tài)同步方法之間,它們只有同一個類的實例對象class!(問題五、問題六)
- 實例對象鎖與類的class對象鎖,是兩個不同的對象,所以靜態(tài)同步方法與非靜態(tài)同步方法之間是不會有競態(tài)條件的。(問題七、問題八)
6. 集合類不安全
6.1 List不安全
-
集合線程的不安全性,如多線程操作ArrayList時,ArrayList在迭代的時候如果同時對其進行修改就會拋出java.util.ConcurrentModificationException異常,并發(fā)修改異常
List<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); }, String.valueOf(i)).start(); }
-
解決方案
- Vector
List<String> list = new Vector<>();
- 加鎖,安全,但性能差
- Collections
-
List<String> list = Collections.synchronizedList(new ArrayList<>());
Collections提供了方法synchronizedList保證list是同步線程安全的 - HashSet 與 HashMap 也是非線程安全的,Collections也提供了對應的方法
-
- 寫時復制CopyOnWriteArrayList
- 類似讀寫分離的思想
- Vector
6.2 寫時復制
-
CopyOnWrite理論
- CopyOnWrite容器即寫時復制的容器。往一個容器添加元素的時候,不直接往當前容器Object[]添加,而是先將當前容器Object[]進行Copy,復制出一個新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。添加元素后,再將原容器的引用指向新的容器setArray(newElements)。這樣做的好處是可以對CopyOnWrite容器進行并發(fā)的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
-
CopyOnWriteArrayList 源碼
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
6.3 Set不安全
-
HashSet線程不安全,底層結構是HashMap,set添加時調用的是map的put方法,值作為key,而value為固定值
public HashSet() { map = new HashMap<>(); } private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT) == null; }
Set<String> set = new HashSet<>();
//線程不安全Set<String> set = Collections.synchronizedSet(new HashSet<>());
//線程安全Set<String> set = new CopyOnWriteArraySet<>();
//線程安全
6.4 Map不安全
6.4.1 HashMap
- 初始容器 16 / 0.75
- 根據(jù)鍵的hashCode值存儲數(shù)據(jù)
- 允許一個鍵為null,允許多個值為null
- HashMap線程不安全,線程安全Collections.synchronizedMap或ConcurrentHashMap
- Java7,數(shù)組+單向鏈表,每個Entry包含4個值:key、value、hash值和用于單向鏈表的next
擴容:當前的2倍,負載因子:0.75 - Java8,數(shù)組+鏈表+紅黑樹,鏈表節(jié)點數(shù)超過8后,鏈表轉為紅黑樹,減少查找鏈表數(shù)據(jù)的時間
- map.put(k,v)實現(xiàn)原理
- 第一步首先將k,v封裝到Node對象當中(節(jié)點)。
- 第二步它的底層會調用K的hashCode()方法得出hash值。
- 第三步通過哈希表函數(shù)/哈希算法,將hash值轉換成數(shù)組的下標,下標位置上如果沒有任何元素,就把Node添加到這個位置上。如果說下標對應的位置上有鏈表。此時,就會拿著k和鏈表上每個節(jié)點的k進行equal。如果所有的equals方法返回都是false,那么這個新的節(jié)點將被添加到鏈表的表頭,next指向之前的表頭節(jié)點。如其中有一個equals返回了true,那么這個節(jié)點的value將會被覆蓋。
- JDK8之后,如果哈希表單向鏈表中元素超過8個,那么單向鏈表這種數(shù)據(jù)結構會變成紅黑樹數(shù)據(jù)結構。當紅黑樹上的節(jié)點數(shù)量小于6個,會重新把紅黑樹變成單向鏈表數(shù)據(jù)結構。
6.4.2 ConcurrentHashMap
- 整個 ConcurrentHashMap 由一個個 Segment 組成,ConcurrentHashMap 是一個 Segment 數(shù)組
- 線程安全,Segment 通過繼承ReentrantLock 來進行加鎖,加鎖鎖住的是Segment
- JDK1.7分段鎖,1.8CAS(compare and swap的縮寫,即我們所說的比較交換)
- JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+紅黑樹
7. Callable接口
7.1 Callable接口
java.util.concurrent包下的函數(shù)式接口
-
與Runnable()對比
方法是否有返回值
是否拋出異常
-
落地方法不同,call() / run()
//創(chuàng)建新類MyThread實現(xiàn)runnable接口 class MyThread implements Runnable{ @Override public void run() { } } //新類MyThread2實現(xiàn)callable接口 class MyThread2 implements Callable<Integer>{ @Override public Integer call() throws Exception { return 200; } }
7.2 FutureTask
- thread構造方法中只能接受Runnable,尋找中間橋梁
A cancellable asynchronous computation. This class provides a base implementation of
Future
, with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. The result can only be retrieved when the computation has completed; theget
methods will block if the computation has not yet completed. Once the computation has completed, the computation cannot be restarted or cancelled (unless the computation is invoked usingrunAndReset()
).A
FutureTask
can be used to wrap aCallable
orRunnable
object. BecauseFutureTask
implementsRunnable
, aFutureTask
can be submitted to anExecutor
for execution.
通過FutureTask的有參構造方法將Callable傳入
運行成功后,通過futureTask.get()獲取返回值
-
在主線程中需要執(zhí)行比較耗時的操作時,但又不想阻塞主線程時,可以把這些作業(yè)交給Future對象在后臺完成,當主線程將來需要時,就可以通過Future對象獲得后臺作業(yè)的計算結果或者執(zhí)行狀態(tài)。一般FutureTask多用于耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法,直到任務轉入完成狀態(tài),然后會返回結果或者拋出異常。一旦計算完成,就不能再重新開始或取消計算。
只計算一次,get方法放到最后。
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> {
System.out.println(Thread.currentThread().getName()+"******");
return 1024;
});
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread());
new Thread(futureTask,"A").start();// 結果會被緩存,效率高
new Thread(futureTask,"B").start();
new Thread(futureTask1,"C").start();
/*while(!futureTask.isDone()){
System.out.println("***wait");
}*/
System.out.println(futureTask.get());
System.out.println(futureTask1.get());
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"******");
return 200;
}
}
/*
執(zhí)行結果:
A******
C******
1024
200
*/
- 有緩存
8. JUC輔助類
8.1 CountDownLatch
- 減少計數(shù)
- 方法
- await() / await(long timeout, TimeUnit unit) (unit 時間單位)
- 阻塞,直到count為0,或線程被中斷,或達到給定的時間
- countDown()
- 計數(shù)減一,直到為0,釋放等待線程
- await() / await(long timeout, TimeUnit unit) (unit 時間單位)
- 當一個或多個線程調用await方法時,這些線程會阻塞,其它線程調用countDown方法會將計數(shù)器減1(調用countDown方法的線程不會阻塞),當計數(shù)器的值變?yōu)?時,因await方法阻塞的線程會被喚醒,繼續(xù)執(zhí)行。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 離開");
count.countDown();
}, String.valueOf(i)).start();
}
count.await();//阻塞,直到count=0,main線程才繼續(xù)向下執(zhí)行
System.out.println("全部離開");
}
}
8.2 CyclicBarrier
- 可循環(huán)(Cyclic)使用的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續(xù)干活。線程進入屏障通過CyclicBarrier的await()方法
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
System.out.println("召喚神龍");
});
for (int i = 1; i <= 7; i++) {
new Thread(() -> {
System.out.println("獲得第"+Thread.currentThread().getName()+"顆龍珠!");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("線程"+Thread.currentThread().getName()+"====>"+
Thread.currentThread().getState());
}, String.valueOf(i)).start();
}
}
}
/*
獲得第1顆龍珠!
獲得第4顆龍珠!
獲得第5顆龍珠!
獲得第3顆龍珠!
獲得第2顆龍珠!
獲得第6顆龍珠!
獲得第7顆龍珠!
召喚神龍
線程5====>RUNNABLE
線程3====>RUNNABLE
線程2====>RUNNABLE
線程1====>RUNNABLE
線程6====>RUNNABLE
線程4====>RUNNABLE
線程7====>RUNNABLE
*/
8.3 Semaphore
- 在信號量上我們定義兩種操作:
- acquire(獲取) 當一個線程調用acquire操作時,它要么通過成功獲取信號量(信號量減1),要么一直等下去,直到有線程釋放信號量,或超時。
- release(釋放)實際上會將信號量的值加1,然后喚醒等待的線程。
- 信號量主要用于兩個目的,一個是用于多個共享資源的互斥使用,另一個用于并發(fā)線程數(shù)的控制。
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t搶到車位!");
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName() + "\t離開");
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
/*
2 搶到車位!
1 搶到車位!
6 搶到車位!
1 離開
6 離開
4 搶到車位!
3 搶到車位!
3 離開
5 搶到車位!
2 離開
4 離開
5 離開
*/
9. ReentrantReadWriteLock讀寫鎖
- ReadWriteLock讀寫鎖(java.util.concurrent.locks包)
- 讀鎖:共享鎖,多個線程可以同時占有,讀的時候可以被多個線程同時讀
- 寫鎖:獨占鎖,一次只能被一個線程占有,寫的時候只能是一個線程寫,寫鎖為排他鎖
- ReentrantReadWriteLock 實現(xiàn) ReadWriteLock接口
- 讀鎖:ReentrantReadWriteLock.ReadLock
- 寫鎖:ReentrantReadWriteLock.WriteLock
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Description 讀寫鎖
* @Package: juc.demo
* @ClassName ReadWriteLockDemo
* @author: wuwb
* @date: 2020/10/21 17:42
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
CacheData cacheData = new CacheData();
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
cacheData.put(num + "", num + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
cacheData.get(num + "");
}, String.valueOf(i)).start();
}
}
}
class CacheData{
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
lock.writeLock().lock();//寫鎖
try {
System.out.println(Thread.currentThread().getName()+"\t 開始寫入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"\t 寫入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void get(String key) {
lock.readLock().lock();//讀鎖
try {
System.out.println(Thread.currentThread().getName()+"\t 開始讀取"+key);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"\t 讀取完成"+result);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
10. 阻塞隊列
10.1 BlockingQueue阻塞隊列
-
棧與隊列
- 棧:先進后出,后進先出
- 隊列:先進先出FIFO
-
BlockingQueue阻塞隊列
- 阻塞:必須要阻塞/不得不阻塞
- 是一個隊列
1608171274821.png- 當隊列是空的,從隊列中獲取元素的操作將會被阻塞
- 試圖從空的隊列中獲取元素的線程將會被阻塞,直到其他線程往空的隊列插入新的元素
- 當隊列是滿的,從隊列中添加元素的操作將會被阻塞
- 試圖向已滿的隊列中添加新元素的線程將會被阻塞,直到其他線程從隊列中移除一個或多個元素或者完全清空,使隊列變得空閑起來并后續(xù)新增
架構
-
BlockingQueue核心方法
添加、移除
方法類型 拋出異常 不拋異常 阻塞等待 超時等待 插入 add(e) offer(e) put(e) offer(e,time,unit) 移除 remove() poll() take() poll(time,unit) 檢查隊首元素 element() peek() - - *檢查隊首元素,可以獲取到隊首元素,但不是移除
- 拋出異常
- 當阻塞隊列滿時,再往隊列里add插入元素會拋IllegalStateException:Queue full
- 當阻塞隊列空時,再往隊列里remove移除元素會拋NoSuchElementException
- 當阻塞隊列空時,再往隊列里element檢查隊首元素會拋NoSuchElementException
- 有返回值,不拋異常
- 插入方法,成功ture失敗false
- 移除方法,成功返回出隊列的元素,隊列里沒有就返回null
- 阻塞等待
- 當阻塞隊列滿時,生產者線程繼續(xù)往隊列里put元素,隊列會一直阻塞生產者線程直到put數(shù)據(jù)or響應中斷退出
- 當阻塞隊列空時,消費者線程試圖從隊列里take元素,隊列會一直阻塞消費者線程直到隊列可用
- 超時等待
- 當阻塞隊列滿時,隊列會阻塞生產者線程一定時間,超過限時后生產者線程會退出
- 當阻塞隊列空時,隊列會阻塞消費者線程一定時間,超過限時后消費者線程會退出
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; /** * @Description 阻塞隊列 * @Package: juc.juc * @ClassName BlockingQueueDemo * @author: wuwb * @date: 2020/12/17 10:41 */ public class BlockingQueueDemo { public static void main(String[] args) throws InterruptedException { //testOne(); //testTwo(); //testThree(); testFour(); } /** * 超時等待 */ private static void testFour() throws InterruptedException { BlockingQueue<String> 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("a",3L, TimeUnit.SECONDS)); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); } /** * 阻塞等待 * @throws InterruptedException */ private static void testThree() throws InterruptedException { BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3); blockingQueue.put("a"); blockingQueue.put("b"); blockingQueue.put("c"); //blockingQueue.put("x"); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); } /** * 不拋出異常 */ private static void testTwo() { BlockingQueue<String> 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")); System.out.println(blockingQueue.peek()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); /*true/true/true/false/a/a/b/c/null*/ } /** * 拋出異常 */ private static void testOne() { BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3); System.out.println(blockingQueue.add("a")); System.out.println(blockingQueue.add("b")); System.out.println(blockingQueue.add("c")); //System.out.println(blockingQueue.add("d")); System.out.println(blockingQueue.element()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); } }
- 拋出異常
10.2 SynchronousQueue 同步隊列
- 沒有容量,進去一個元素,必須等待取出來之后,才能再往里面放一個元素
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步隊列
* 和其他的BlockingQueue 不一樣, SynchronousQueue 不存儲元素
* put了一個元素,必須從里面先take取出來,否則不能在put進去值!
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = 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) {
e.printStackTrace();
}
}, "AA").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BB").start();
}
}
11. ThreadPool線程池
11.1 線程池的使用
-
線程復用、控制最大并發(fā)數(shù)、管理線程
- 降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的銷耗。
- 提高響應速度。當任務到達時,任務可以不需要等待線程創(chuàng)建就能立即執(zhí)行。
- 提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會銷耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調優(yōu)和監(jiān)控。
-
線程池架構
1608252489878.png? Executor,Executors,ExecutorService,ThreadPoolExecutor
11.1 三大方法
-
Executors.newFixedThreadPool(int)
- 執(zhí)行長期任務性能好,創(chuàng)建一個線程池,一池有N個固定的線程,有固定線程數(shù)的線程
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
- newFixedThreadPool創(chuàng)建的線程池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue
-
Executors.newSingleThreadExecutor()
- 一個任務一個任務的執(zhí)行,一池一線程
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
- newSingleThreadExecutor 創(chuàng)建的線程池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue
-
Executors.newCachedThreadPool()
- 執(zhí)行很多短期異步任務,線程池根據(jù)需要創(chuàng)建新線程,但在先前構建的線程可用時將重用它們。可擴容,遇強則強
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
- newCachedThreadPool創(chuàng)建的線程池將corePoolSize設置為0,將maximumPoolSize設置為Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是說來了任務就創(chuàng)建線程運行,當線程空閑超過60秒,就銷毀線程。
從以上源碼可以看出,三大方法底層均是使用ThreadPoolExecutor()來創(chuàng)建線程池
代碼
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 線程池
* Arrays
* Collections
* Executors
*/
public class MyThreadPoolDemo {
public static void main(String[] args) {
//List list = new ArrayList();
//List list = Arrays.asList("a","b");
//固定數(shù)的線程池,一池五線程
//一個銀行網點,5個受理業(yè)務的窗口
//ExecutorService threadPool = Executors.newFixedThreadPool(5);
//一個銀行網點,1個受理業(yè)務的窗口
//ExecutorService threadPool = Executors.newSingleThreadExecutor();
//一個銀行網點,可擴展受理業(yè)務的窗口
ExecutorService threadPool = Executors.newCachedThreadPool();
//10個顧客請求
try {
for (int i = 1; i <=10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 辦理業(yè)務");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
-
工作中均不使用以上三大方法來創(chuàng)建線程池,而是直接使用ThreadPoolExecutor()來創(chuàng)建線程池
1608256649626.png
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
//new ThreadPoolExecutor.AbortPolicy()
//new ThreadPoolExecutor.CallerRunsPolicy()
//new ThreadPoolExecutor.DiscardOldestPolicy()
new ThreadPoolExecutor.DiscardOldestPolicy()
);
//10個顧客請求
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t 辦理業(yè)務");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
11.2 七大參數(shù)
- ThreadPoolExecutor()源碼
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 七大參數(shù)
- corePoolSize:線程池中的常駐核心線程數(shù)
- maximumPoolSize:線程池中能夠容納同時執(zhí)行的最大線程數(shù),此值必須大于等于1
- keepAliveTime:多余的空閑線程的存活時間,當前池中線程數(shù)量超過corePoolSize時,當空閑時間達到keepAliveTime時,多余線程會被銷毀直到只剩下corePoolSize個線程為止
- unit:keepAliveTime的單位
- workQueue:任務隊列,被提交但尚未被執(zhí)行的任務
- threadFactory:表示生成線程池中工作線程的線程工廠,用于創(chuàng)建線程,一般默認的即可
- handler:拒絕策略,表示當隊列滿了,并且工作線程大于等于線程池的最大線程數(shù)(maximumPoolSize)時如何來拒絕請求執(zhí)行的runnable的策略
11.3 四種拒絕策略
拒絕策略:等待隊列已經排滿了,再也塞不下新任務了,同時,線程池中的max線程也達到了,無法繼續(xù)為新任務服務。這個是時候我們就需要拒絕策略機制合理的處理這個問題。
JDK內置四種拒絕策略
- AbortPolicy(默認):直接拋出RejectedExecutionException異常阻止系統(tǒng)正常運行
- CallerRunsPolicy:“調用者運行”一種調節(jié)機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者,從而降低新任務的流量。
- DiscardOldestPolicy:拋棄隊列中等待最久的任務,然后把當前任務加人隊列中嘗試再次提交當前任務。
- DiscardPolicy:該策略默默地丟棄無法處理的任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的一種策略。
/**
* new ThreadPoolExecutor.AbortPolicy() // 銀行滿了,還有人進來,不處理這個人的,拋出異常
* new ThreadPoolExecutor.CallerRunsPolicy() // 哪來的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //隊列滿了,丟掉任務,不會拋出異常!
* new ThreadPoolExecutor.DiscardOldestPolicy() //隊列滿了,嘗試去和最早的競爭,也不會拋出異常!
*/
- 以上內置拒絕策略均實現(xiàn)了RejectedExecutionHandle接口
11.4 線程池底層運行原理
在創(chuàng)建了線程池后,開始等待請求。
-
當調用execute()方法添加一個請求任務時,線程池會做出如下判斷:
- 1如果正在運行的線程數(shù)量小于corePoolSize,那么馬上創(chuàng)建線程運行這個任務;
- 2如果正在運行的線程數(shù)量大于或等于corePoolSize,那么將這個任務放入隊列;
- 3如果這個時候隊列滿了且正在運行的線程數(shù)量還小于maximumPoolSize,那么還是要創(chuàng)建非核心線程立刻運行這個任務(隊列中的依舊在等待);
- 4如果隊列滿了且正在運行的線程數(shù)量大于或等于maximumPoolSize,那么線程池會啟動飽和拒絕策略來執(zhí)行。
當一個線程完成任務時,它會從隊列中取下一個任務來執(zhí)行。
-
當一個線程無事可做超過一定的時間(keepAliveTime)時,線程會判斷:
- 如果當前運行的線程數(shù)大于corePoolSize,那么這個線程就被停掉。所以線程池的所有任務完成后,它最終會收縮到corePoolSize的大小。
-
舉例:
- 銀行有5個窗口(maximumPoolSize),2個啟用(corePoolSize),3個暫停服務,且等待區(qū)有5張椅子供等待使用(workQueue),開始時前面進來的2個客戶直接到啟用的2個窗口辦理業(yè)務,后面來的客戶,先到等待區(qū)椅子上等待,當?shù)却齾^(qū)5張椅子坐滿后,又有人進來辦業(yè)務,于是銀行就啟用另外3個窗口進行服務,辦理完業(yè)務的窗口,直接喊等待區(qū)的人去那個窗口辦理,當5個窗口都在服務,且等待區(qū)也滿的時候,銀行只能讓保安在門口堵著(RejectedExecutionHandler),拒絕后面的人員進入(畢竟疫情期間不能擠在一起嘛!!!)
import java.util.concurrent.*; /** * @Description TODO * @Package: juc.juc * @ClassName ThreadPoolDemo * @author: wuwb * @date: 2020/12/18 9:12 */ public class ThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 2L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ); try { for (int i = 1; i <= 10; i++) { final int j = i; threadPool.execute(()->{ System.out.println(Thread.currentThread().getName() + " run " + j); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } } /* pool-1-thread-1 run 1 pool-1-thread-2 run 2 pool-1-thread-3 run 8 pool-1-thread-4 run 9 pool-1-thread-5 run 10 pool-1-thread-1 run 3 pool-1-thread-4 run 4 pool-1-thread-2 run 5 pool-1-thread-3 run 6 pool-1-thread-5 run 7 1、2進核心線程,3、4、5、6、7進隊列等待,8、9、10啟用非核心線程先于隊列中任務運行 */
*CPU密集型 最大線程數(shù)為CPU核數(shù),CPU核數(shù)Runtime.getRuntime().availableProcessors();
12. 函數(shù)式接口
位置:java.util.function包下
接口中只能有一個抽象方法的接口,稱函數(shù)式接口
java內置核心四大函數(shù)式接口:
函數(shù)式接口 | 參數(shù)類型 | 返回類型 | 用途 |
---|---|---|---|
Consumer<T> 消費型 | T | void | 對類型為T的對象應用操作,包含方法:void accept(T t) |
Supplier<T> 供給型 | 無 | T | 返回類型為T的對象,包含方法:T get(); |
Function<T, R> 函數(shù)型 | T | R | 對類型為T的對象應用操作,并返回結果;<br />結果是R類型的對象,包含方法:R apply(T t) |
Predicate<T> 斷定型 | T | boolean | 確定類型為T的對象是否滿足某約束,并返回boolean值,包含方法:boolean test(T t) |
-
消費型Consumer<T>
- 對類型為T的對象應用操作
1608278667290.png/** * 消費型 */ public static void testConsumer() { /*Consumer consumer = new Consumer<String>() { @Override public void accept(String s) { System.out.println("輸入的值是:" + s); } };*/ Consumer consumer = (s) -> { System.out.println("輸入的值是:" + s); }; consumer.accept("abc"); }
供給型Supplier<T>
- 返回類型為T的對象

```java
/**
* 供給型
*/
public static void testSupplier() {
/*Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "供給型接口";
}
};*/
Supplier<String> supplier = () -> {
return "供給型接口lambda";
};
System.out.println(supplier.get());
}
```
-
函數(shù)型Function<T, R>
- 對類型為T的對象應用操作,并返回結果,結果是R類型的對象
/**
* 函數(shù)型
*/
public static void testFunction() {
/*Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String s) {
s += "123";
return s;
}
};*/
Function<String, String> function = (s) -> {
s += "123lambda";
return s;
};
System.out.println(function.apply("abc"));
}
-
斷定型Predicate<T>
- 確定類型為T的對象是否滿足某約束,并返回boolean值
/**
* 斷定型
*/
public static void testPredicate() {
/*Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return "1024".equals(s);
}
};*/
Predicate<String> predicate = (s) -> {
return "1024".equals(s);
};
System.out.println(predicate.test("1024"));
}