国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

JUC 高并發(fā)編程基礎篇

這篇具有很好參考價值的文章主要介紹了JUC 高并發(fā)編程基礎篇。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

JUC 高并發(fā)編程基礎篇

? 1、什么是 JUC
? 2、Lock 接口
? 3、線程間通信
? 4、集合的線程安全
? 5、多線程鎖
? 6、Callable 接口
? 7、JUC 三大輔助類: CountDownLatch CyclicBarrier Semaphore
? 8、讀寫鎖: ReentrantReadWriteLock
? 9、阻塞隊列
? 10、ThreadPool 線程池
? 11、Fork/Join 框架
? 12、CompletableFuture

1 什么是 JUC
1.1 JUC 簡介
在 Java 中,線程部分是一個重點, JUC 也是關于線程的。JUC
就是 java.util .concurrent 工具包的簡稱
。這是一個處理線程的工具包,JDK 1.5 開始出現的。
JUC 高并發(fā)編程基礎篇
1.2 進程與線程

進程(Process) 是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。 在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。程序是指令、數據及其組織形式的描述,進程是程序的實體。

線程(thread) 是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務。

總結來說:

進程:指在系統中正在運行的一個應用程序;程序一旦運行就是進程;進程——資源分配的最小單位。

線程:系統分配處理器時間資源的基本單元,或者說進程之內獨立執(zhí)行的一個單元執(zhí)行流。線程——程序執(zhí)行的最小單位。

1.3 線程的狀態(tài) 
1.3.1 線程狀態(tài)枚舉類 
Thread.State

public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,(新建)
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,(準備就緒)
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,(阻塞)
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,(不見不散)
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,(過時不候)
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;(終結)
}

1.3.2 wait/sleep 的區(qū)別
(1)sleep 是 Thread 的靜態(tài)方法,wait 是 Object 的方法,任何對象實例都能調用。
(2)sleep 不會釋放鎖,它也不需要占用鎖。wait 會釋放鎖,但調用它的前提是當前線程占有鎖(即代碼要在 synchronized 中)。
(3)它們都可以被 interrupted 方法中斷。

1.4 并發(fā)與并行
1.4.1 串行模式
串行表示所有任務都一一按先后順序進行。串行意味著必須先裝完一車柴才能運送這車柴,只有運送到了,才能卸下這車柴,并且只有完成了這整個三個步驟,才能進行下一個步驟。
串行是一次只能取得一個任務,并執(zhí)行這個任務。

1.4.2 并行模式
并行意味著可以同時取得多個任務,并同時去執(zhí)行所取得的這些任務。并行模式相當于將長長的一條隊列,劃分成了多條短隊列,所以并行縮短了任務隊列的長度。并行的效率從代碼層次上強依賴于多進程/多線程代碼,從硬件角度上則依賴于多核 CPU。

1.4.3 并發(fā)
并發(fā)(concurrent)指的是多個程序可以同時運行的現象,更細化的是多進程可以同時運行或者多指令可以同時運行。但這不是重點,在描述并發(fā)的時候也不會去扣這種字眼是否精確,并發(fā)的重點在于它是一種現象, 并發(fā)描述的是多進程同時運行的現象。但實際上,對于單核心 CPU 來說,同一時刻只能運行一個線程。所以,這里的"同時運行"表示的不是真的同一時刻有多個線程運行的現象,這是并行的概念,而是提供一種功能讓用戶看來多個程序同時運行起來了,但實際上這些程序中的進程不是一直霸占 CPU 的,而是執(zhí)行一會停一會。
要解決大并發(fā)問題,通常是將大任務分解成多個小任務, 由于操作系統對進程的調度是隨機的,所以切分成多個小任務后,可能會從任一小任務處執(zhí)行。這可能會出現一些現象:

? 可能出現一個小任務執(zhí)行了多次,還沒開始下個任務的情況。這時一般會采用隊列或類似的數據結構來存放各個小任務的成果
? 可能出現還沒準備好第一步就執(zhí)行第二步的可能。這時,一般采用多路復用或異步的方式,比如只有準備好產生了事件通知才執(zhí)行某個任務。
? 可以多進程/多線程的方式并行執(zhí)行這些小任務。也可以單進程/單線程執(zhí)行這些小任務,這時很可能要配合多路復用才能達到較高的效率

1.4.4 小結(重點)

并發(fā):同一時刻多個線程在訪問同一個資源,多個線程對一個點

例子:春運搶票 電商秒殺…

并行:多項工作一起執(zhí)行,之后再匯總

例子:泡方便面,電水壺燒水,一邊撕調料倒入桶中

1.5 管程
管程(monitor)是保證了同一時刻只有一個進程在管程內活動,即管程內定義的操作在同一時刻只被一個進程調用(由編譯器實現).但是這樣并不能保證進程以設計的順序執(zhí)行JVM 中同步是基于進入和退出管程(monitor)對象實現的,每個對象都會有一個管程(monitor)對象,管程(monitor)會隨著 java 對象一同創(chuàng)建和銷毀執(zhí)行線程首先要持有管程對象,然后才能執(zhí)行方法,當方法完成之后會釋放管程,方法在執(zhí)行時候會持有管程,其他線程無法再獲取同一個管程

1.6 用戶線程和守護線程
用戶線程:平時用到的普通線程,自定義線程
守護線程:運行在后臺,是一種特殊的線程,比如垃圾回收
當主線程結束后,用戶線程還在運行,JVM 存活
如果沒有用戶線程,都是守護線程,JVM 結束

2 Lock 接口
2.1 Synchronized
2.1.1 Synchronized 關鍵字回顧
synchronized 是 Java 中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:

  1. 修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;
  2. 修飾一個方法,被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象;
    o 雖然可以使用 synchronized 來定義方法,但 synchronized 并不屬于方法定義的一部分,因此,synchronized 關鍵字不能被繼承。如果在父類中的某個方法使用了 synchronized 關鍵字,而在子類中覆蓋了這個方法,在子類中的這個方法默認情況下并不是同步的,而必須顯式地在子類的這個方法中加上synchronized 關鍵字才可以。當然,還可以在子類方法中調用父類中相應的方法,這樣雖然子類中的方法不是同步的,但子類調用了父類的同步方法,因此,子類的方法也就相當于同步了。
  3. 修改一個靜態(tài)的方法,其作用的范圍是整個靜態(tài)方法,作用的對象是這個類的所有對象;
  4. 修改一個類,其作用的范圍是 synchronized 后面括號括起來的部分,作用主的對象是這個類的所有對象。
    2.1.2 售票案例
class Ticket {
 //票數
 private int number = 30;
 //操作方法:賣票
 public synchronized void sale() {
 //判斷:是否有票
 if(number > 0) {
 System.out.println(Thread.currentThread().getName()+" : 
"+(number--)+" "+number);
 }
 }
}

如果一個代碼塊被 synchronized 修飾了,當一個線程獲取了對應的鎖,并執(zhí)行該代碼塊時,其他線程便只能一直等待,等待獲取鎖的線程釋放鎖,而這里獲取鎖的線程釋放鎖只會有兩種情況:
1)獲取鎖的線程執(zhí)行完了該代碼塊,然后線程釋放對鎖的占有;
2)線程執(zhí)行發(fā)生異常,此時 JVM 會讓線程自動釋放鎖。
那么如果這個獲取鎖的線程由于要等待 IO 或者其他原因(比如調用 sleep方法)被阻塞了,但是又沒有釋放鎖,其他線程便只能干巴巴地等待,試想一下,這多么影響程序執(zhí)行效率。
因此就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過 Lock 就可以辦到。
2.2 什么是 Lock
Lock 鎖實現提供了比使用同步方法和語句可以獲得的更廣泛的鎖操作。它們允許更靈活的結構,可能具有非常不同的屬性,并且可能支持多個關聯的條件對象。Lock 提供了比 synchronized 更多的功能。

Lock 與的 Synchronized 區(qū)別
? Lock 不是 Java 語言內置的,synchronized 是 Java 語言的關鍵字,因此是內置特性。Lock 是一個類,通過這個類可以實現同步訪問;
? Lock 和 synchronized 有一點非常大的不同,采用 synchronized 不需要用戶去手動釋放鎖,當 synchronized 方法或者 synchronized 代碼塊執(zhí)行完之后,系統會自動讓線程釋放對鎖的占用;而 Lock 則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。
2.2.1 Lock 接口

public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}

下面來逐個講述 Lock 接口中每個方法的使用
2.2.2 lock
lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。
采用 Lock,必須主動去釋放鎖,并且在發(fā)生異常時,不會自動釋放鎖。因此一般來說,使用 Lock 必須在 try{}catch{}塊中進行,并且將釋放鎖的操作放在finally 塊中進行,以保證鎖一定被被釋放,防止死鎖的發(fā)生。通常使用 Lock來進行同步的話,是以下面這種形式去使用的:

Lock lock = ...;
lock.lock();
try{
//處理任務
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}

2.2.3 newCondition
關鍵字 synchronized 與 wait()/notify()這兩個方法一起使用可以實現等待/通知模式, Lock 鎖的 newContition()方法返回 Condition 對象,Condition 類也可以實現等待/通知模式。
用 notify()通知時,JVM 會隨機喚醒某個等待的線程, 使用 Condition 類可以進行選擇性通知, Condition 比較常用的兩個方法:
? await()會使當前線程等待,同時會釋放鎖,當其他線程調用 signal()時,線程會重新獲得鎖并繼續(xù)執(zhí)行。
? signal()用于喚醒一個等待的線程。
注意:在調用 Condition 的 await()/signal()方法前,也需要線程持有相關
的 Lock 鎖,調用 await()后線程會釋放這個鎖,在 singal()調用后會從當前
Condition 對象的等待隊列中,喚醒 一個線程,喚醒的線程嘗試獲得鎖, 一旦
獲得鎖成功就繼續(xù)執(zhí)行。

2.3 ReentrantLock
ReentrantLock,意思是“可重入鎖”,關于可重入鎖的概念將在后面講述。
ReentrantLock 是唯一實現了 Lock 接口的類,并且 ReentrantLock 提供了更
多的方法。下面通過一些實例看具體看一下如何使用。

public class Test {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
public static void main(String[] args) {
final Test test = new Test();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
} 
public void insert(Thread thread) {
Lock lock = new ReentrantLock(); //注意這個地方
lock.lock();
try {
System.out.println(thread.getName()+"得到了鎖");
for(int i=0;i<5;i++) {
arrayList.add(i);
}
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()+"釋放了鎖");
lock.unlock();
}
}
}
2.4 ReadWriteLock
ReadWriteLock 也是一個接口,在它里面只定義了兩個方法:
public interface ReadWriteLock {
 /**
 * Returns the lock used for reading.
 *
 * @return the lock used for reading.
 */
 Lock readLock();
 /**
 * Returns the lock used for writing.
 *
 * @return the lock used for writing.
 */
 Lock writeLock();
}

一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操作分開,分
成 2 個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。下面的
ReentrantReadWriteLock 實現了 ReadWriteLock 接口。
ReentrantReadWriteLock 里面提供了很多豐富的方法,不過最主要的有兩個
方法:readLock()和 writeLock()用來獲取讀鎖和寫鎖。
下面通過幾個例子來看一下 ReentrantReadWriteLock 具體用法。
假如有多個線程要同時進行讀操作的話,先看一下 synchronized 達到的效果:

public class Test {
 private ReentrantReadWriteLock rwl = new 
ReentrantReadWriteLock();
 
 public static void main(String[] args) {
 final Test test = new Test();
 
 new Thread(){
 public void run() {
 test.get(Thread.currentThread());
 };
 }.start();
 
 new Thread(){
 public void run() {
 test.get(Thread.currentThread());
 };
 }.start();
 
 } 
 
 public synchronized void get(Thread thread) {
 long start = System.currentTimeMillis();
 while(System.currentTimeMillis() - start <= 1) {
 System.out.println(thread.getName()+"正在進行讀操作");
 }
 System.out.println(thread.getName()+"讀操作完畢");
 }
}
而改成用讀寫鎖的話:
public class Test {
 private ReentrantReadWriteLock rwl = new 
ReentrantReadWriteLock();
 
 public static void main(String[] args) {
 final Test test = new Test();
 
 new Thread(){
 public void run() {
 test.get(Thread.currentThread());
 };
 }.start();
 
 new Thread(){
 public void run() {
 test.get(Thread.currentThread());
 };
 }.start();
 
 } 
 
 public void get(Thread thread) {
 rwl.readLock().lock();
 try {
 long start = System.currentTimeMillis();
 
 while(System.currentTimeMillis() - start <= 1) {
 System.out.println(thread.getName()+"正在進行讀操作");
 }
 System.out.println(thread.getName()+"讀操作完畢");
 } finally {
 rwl.readLock().unlock();
 }
 }
}

說明 thread1 和 thread2 在同時進行讀操作。這樣就大大提升了讀操作的效率。
注意:
? 如果有一個線程已經占用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫
鎖的線程會一直等待釋放讀鎖。
? 如果有一個線程已經占用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則
申請的線程會一直等待釋放寫鎖。

2.5 小結(重點)
Lock 和 synchronized 有以下幾點不同:

  1. Lock 是一個接口,而 synchronized 是 Java 中的關鍵字,synchronized 是內置的語言實現;
  2. synchronized 在發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發(fā)生;而 Lock 在發(fā)生異常時,如果沒有主動通過 unLock()去釋放鎖,則很可能造成死鎖現象,因此使用 Lock 時需要在 finally 塊中釋放鎖;
  3. Lock 可以讓等待鎖的線程響應中斷,而 synchronized 卻不行,使用
    synchronized 時,等待的線程會一直等待下去,不能夠響應中斷;
  4. 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
  5. Lock 可以提高多個線程進行讀操作的效率。
    在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時 Lock 的性能要遠遠優(yōu)于synchronized。

3 線程間通信
線程間通信的模型有兩種:共享內存和消息傳遞,以下方式都是基本這兩種模型來實現的。我們來基本一道面試常見的題目來分析
場景—兩個線程,一個線程對當前數值加 1,另一個線程對當前數值減 1,要求用線程間通信
3.1 synchronized 方案

package com.atguigu.test;
/**
* volatile 關鍵字實現線程交替加減
*/
public class TestVolatile {
 /**
 * 交替加減
 * @param args
 */
 public static void main(String[] args){
 DemoClass demoClass = new DemoClass();
 new Thread(() ->{
 for (int i = 0; i < 5; i++) {
 demoClass.increment();
 }
 }, "線程 A").start();
 new Thread(() ->{
 for (int i = 0; i < 5; i++) {
 demoClass.decrement();
 }
 }, "線程 B").start();
 }
}
package com.atguigu.test;
class DemoClass{
 //加減對象
 private int number = 0;
 /**
 * 加 1
 */
 public synchronized void increment() {
 try {
 while (number != 0){
 this.wait();
 }
 number++;
 System.out.println("--------" + Thread.currentThread().getName() + "加一成
功----------,值為:" + number);
 notifyAll();
 }catch (Exception e){
 e.printStackTrace();
 }
 }
 /**
 * 減一
 */
 public synchronized void decrement(){
 try {
 while (number == 0){
 this.wait();
 }
 number--;
 System.out.println("--------" + Thread.currentThread().getName() + "減一成
功----------,值為:" + number);
 notifyAll();
 }catch (Exception e){
 e.printStackTrace();
 }
 }
}

3.2 Lock 方案

package com.atguigu.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class DemoClass{
 //加減對象
 private int number = 0;
 //聲明鎖
 private Lock lock = new ReentrantLock();
 //聲明鑰匙
 private Condition condition = lock.newCondition();
 /**
 * 加 1
 */
 public void increment() {
 try {
 lock.lock();
 while (number != 0){
 condition.await();
 }
 number++;
 System.out.println("--------" + Thread.currentThread().getName() + "加一成
功----------,值為:" + number);
 condition.signalAll();
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 lock.unlock();
 }
 }
 /**
 * 減一
 */
 public void decrement(){
 try {
 lock.lock();
 while (number == 0){
 condition.await();
 }
 number--;
 System.out.println("--------" + Thread.currentThread().getName() + "減一成
功----------,值為:" + number);
 condition.signalAll();
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 lock.unlock();
 }
 }
}

3.4 線程間定制化通信
4.4.1 案例介紹
問題: A 線程打印 5 次 A,B 線程打印 10 次 B,C 線程打印 15 次 C,按照
此順序循環(huán) 10 輪

4.4.2 實現流程

package com.atguigu.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class DemoClass{
 //通信對象:0--打印 A 1---打印 B 2----打印 C
 private int number = 0;
 //聲明鎖
 private Lock lock = new ReentrantLock();
 //聲明鑰匙 A
 private Condition conditionA = lock.newCondition();
 //聲明鑰匙 B
 private Condition conditionB = lock.newCondition();
 //聲明鑰匙 C
 private Condition conditionC = lock.newCondition();
 /**
 * A 打印 5 次
 */
 public void printA(int j){
 try {
 lock.lock();
 while (number != 0){
 conditionA.await();
 }
 System.out.println(Thread.currentThread().getName() + "輸出 A,第" + j + "
輪開始");
 //輸出 5 次 A
 for (int i = 0; i < 5; i++) {
 System.out.println("A");
 }
 //開始打印 B
 number = 1;
 //喚醒 B
 conditionB.signal();
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 lock.unlock();
 }
 }
 /**
 * B 打印 10 次
 */
 public void printB(int j){
 try {
 lock.lock();
 while (number != 1){
 conditionB.await();
 }
 System.out.println(Thread.currentThread().getName() + "輸出 B,第" + j + "
輪開始");
 //輸出 10 次 B
 for (int i = 0; i < 10; i++) {
 System.out.println("B");
 }
 //開始打印 C
 number = 2;
 //喚醒 C
 conditionC.signal();
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 lock.unlock();
 }
 }
 /**
 * C 打印 15 次
 */
 public void printC(int j){
 try {
 lock.lock();
 while (number != 2){
 conditionC.await();
 }
 System.out.println(Thread.currentThread().getName() + "輸出 C,第" + j + "
輪開始");
 //輸出 15 次 C
 for (int i = 0; i < 15; i++) {
 System.out.println("C");
 }
 System.out.println("-----------------------------------------");
 //開始打印 A
 number = 0;
 //喚醒 A
 conditionA.signal();
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 lock.unlock();
 }
 }
}
測試類
package com.atguigu.test;
/**
* volatile 關鍵字實現線程交替加減
*/
public class TestVolatile {
 /**
 * 交替加減
 * @param args
 */
 public static void main(String[] args){
 DemoClass demoClass = new DemoClass();
 new Thread(() ->{
 for (int i = 1; i <= 10; i++) {
 demoClass.printA(i);
 }
 }, "A 線程").start();
 new Thread(() ->{
 for (int i = 1; i <= 10; i++) {
 demoClass.printB(i);
 }
 }, "B 線程").start();
 new Thread(() ->{
 for (int i = 1; i <= 10; i++) {
 demoClass.printC(i);
 }
 }, "C 線程").start();
 }
}

5 多線程鎖
5.1 鎖的八個問題演示

class Phone {
 public static synchronized void sendSMS() throws Exception {
 //停留 4 秒
 TimeUnit.SECONDS.sleep(4);
 System.out.println("------sendSMS");
 }
 public synchronized void sendEmail() throws Exception {
 System.out.println("------sendEmail");
 }
 public void getHello() {
 System.out.println("------getHello");
 }
}
/**
* @Description: 8 鎖
*
1 標準訪問,先打印短信還是郵件
------sendSMS
------sendEmail
2 停 4 秒在短信方法內,先打印短信還是郵件
------sendSMS
------sendEmail
3 新增普通的 hello 方法,是先打短信還是 hello
------getHello
------sendSMS
4 現在有兩部手機,先打印短信還是郵件
------sendEmail
------sendSMS
5 兩個靜態(tài)同步方法,1 部手機,先打印短信還是郵件
------sendSMS
------sendEmail
6 兩個靜態(tài)同步方法,2 部手機,先打印短信還是郵件
------sendSMS
------sendEmail
7 1 個靜態(tài)同步方法,1 個普通同步方法,1 部手機,先打印短信還是郵件
------sendEmail
------sendSMS
8 1 個靜態(tài)同步方法,1 個普通同步方法,2 部手機,先打印短信還是郵件
------sendEmail
------sendSMS
結論:
一個對象里面如果有多個 synchronized 方法,某一個時刻內,只要一個線程去調用其中的
一個 synchronized 方法了,
其它的線程都只能等待,換句話說,某一個時刻內,只能有唯一一個線程去訪問這些
synchronized 方法
鎖的是當前對象 this,被鎖定后,其它的線程都不能進入到當前對象的其它的
synchronized 方法
加個普通方法后發(fā)現和同步鎖無關
換成兩個對象后,不是同一把鎖了,情況立刻變化。
synchronized 實現同步的基礎:Java 中的每一個對象都可以作為鎖。
具體表現為以下 3 種形式。
對于普通同步方法,鎖是當前實例對象。
對于靜態(tài)同步方法,鎖是當前類的 Class 對象。
對于同步方法塊,鎖是 Synchonized 括號里配置的對象
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。
也就是說如果一個實例對象的非靜態(tài)同步方法獲取鎖后,該實例對象的其他非靜態(tài)同步方
法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,
可是別的實例對象的非靜態(tài)同步方法因為跟該實例對象的非靜態(tài)同步方法用的是不同的鎖,
所以毋須等待該實例對象已獲取鎖的非靜態(tài)同步方法釋放鎖就可以獲取他們自己的鎖。
所有的靜態(tài)同步方法用的也是同一把鎖——類對象本身,這兩把鎖是兩個不同的對象,所
以靜態(tài)同步方法與非靜態(tài)同步方法之間是不會有競態(tài)條件的。
但是一旦一個靜態(tài)同步方法獲取鎖后,其他的靜態(tài)同步方法都必須等待該方法釋放鎖后才
能獲取鎖,而不管是同一個實例對象的靜態(tài)同步方法之間,還是不同的實例對象的靜態(tài)同
步方法之間,只要它們同一個類的實例對象!

6 Callable&Future 接口
6.1 Callable 接口
目前我們學習了有兩種創(chuàng)建線程的方法-一種是通過創(chuàng)建 Thread 類,另一種是
通過使用 Runnable 創(chuàng)建線程。但是,Runnable 缺少的一項功能是,當線程
終止時(即 run()完成時),我們無法使線程返回結果。為了支持此功能,
Java 中提供了 Callable 接口。
現在我們學習的是創(chuàng)建線程的第三種方案—Callable 接口
Callable 接口的特點如下(重點)
? 為了實現 Runnable,需要實現不返回任何內容的 run()方法,而對于
Callable,需要實現在完成時返回結果的 call()方法。
? call()方法可以引發(fā)異常,而 run()則不能。
? 為實現 Callable 而必須重寫 call 方法
? 不能直接替換 runnable,因為 Thread 類的構造方法根本沒有 Callable

創(chuàng)建新類 MyThread 實現 runnable 接口

class MyThread implements Runnable{
@Override
public void run() {
}
}

新類 MyThread2 實現 callable 接口

class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}

6.2 Future 接口 當 call()方法完成時,結果必須存儲在主線程已知的對象中,以便主線程可 以知道該線程返回的結果。為此,可以使用 Future 對象。 將 Future 視為保存結果的對象–它可能暫時不保存結果,但將來會保存(一旦 Callable 返回)。Future 基本上是主線程可以跟蹤進度以及其他線程的結果的 一種方式。要實現此接口,必須重寫 5 種方法,這里列出了重要的方法,如下: ? public boolean cancel(boolean mayInterrupt):用于停止任務。如果尚未啟動,它將停止任務。如果已啟動,則僅在 mayInterrupt 為 true 時才會中斷任務。 ? public Object get()拋出 InterruptedException,ExecutionException: 用于獲取任務的結果。 ==如果任務完成,它將立即返回結果,否則將等待任務完成,然后返回結果。

? public boolean isDone():如果任務完成,則返回 true,否則返回 false
可以看到 Callable 和 Future 做兩件事-Callable 與 Runnable 類似,因為它封
裝了要在另一個線程上運行的任務,而 Future 用于存儲從另一個線程獲得的結
果。實際上,future 也可以與 Runnable 一起使用。
要創(chuàng)建線程,需要 Runnable。為了獲得結果,需要 future。
6.3 FutureTask
Java 庫具有具體的 FutureTask 類型,該類型實現 Runnable 和 Future,并方
便地將兩種功能組合在一起。 可以通過為其構造函數提供 Callable 來創(chuàng)建
FutureTask。然后,將 FutureTask 對象提供給 Thread 的構造函數以創(chuàng)建
Thread 對象。因此,間接地使用 Callable 創(chuàng)建線程。
核心原理:(重點)
在主線程中需要執(zhí)行比較耗時的操作時,但又不想阻塞主線程時,可以把這些
作業(yè)交給 Future 對象在后臺完成
? 當主線程將來需要時,就可以通過 Future 對象獲得后臺作業(yè)的計算結果或者執(zhí)
行狀態(tài)
? 一般 FutureTask 多用于耗時的計算,主線程可以在完成自己的任務后,再去
獲取結果。
? 僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法
? 一旦計算完成,就不能再重新開始或取消計算
? get 方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完
成狀態(tài),然后會返回結果或者拋出異常
? get 只計算一次,因此 get 方法放到最后
demo 案例
6.4 使用 Callable 和 Future
CallableDemo 案例

/**
* CallableDemo 案列
*/
public class CallableDemo {
 /**
 * 實現 runnable 接口
 */
 static class MyThread1 implements Runnable{
 /**
 * run 方法
 */
 @Override
 public void run() {
 try {
 System.out.println(Thread.currentThread().getName() + "線程進入了 run
方法");
 }catch (Exception e){
 e.printStackTrace();
 }
 }
 }
 /**
 * 實現 callable 接口
 */
 static class MyThread2 implements Callable{
 /**
 * call 方法
 * @return
 * @throws Exception
 */
 @Override
 public Long call() throws Exception {
 try {
 System.out.println(Thread.currentThread().getName() + "線程進入了 call
方法,開始準備睡覺");
 Thread.sleep(1000);
 System.out.println(Thread.currentThread().getName() + "睡醒了");
 }catch (Exception e){
 e.printStackTrace();
 }
 return System.currentTimeMillis();
 }
 }
 public static void main(String[] args) throws Exception{
 //聲明 runable
 Runnable runable = new MyThread1();
 //聲明 callable
 Callable callable = new MyThread2();
 //future-callable
 FutureTask<Long> futureTask2 = new FutureTask(callable);
 //線程二
 new Thread(futureTask2, "線程二").start();
 for (int i = 0; i < 10; i++) {
 Long result1 = futureTask2.get();
 System.out.println(result1);
 }
 //線程一
 new Thread(runable,"線程一").start();
 }
}

6.5 小結(重點)
? 在主線程中需要執(zhí)行比較耗時的操作時,但又不想阻塞主線程時,可以把這些
作業(yè)交給 Future 對象在后臺完成, 當主線程將來需要時,就可以通過 Future
對象獲得后臺作業(yè)的計算結果或者執(zhí)行狀態(tài)
? 一般 FutureTask 多用于耗時的計算,主線程可以在完成自己的任務后,再去
獲取結果
? 僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法。一旦計
算完成,就不能再重新開始或取消計算。get 方法而獲取結果只有在計算完成
時獲取,否則會一直阻塞直到任務轉入完成狀態(tài),然后會返回結果或者拋出異
常。
? 只計算一次

7 JUC 三大輔助類
JUC 中提供了三種常用的輔助類,通過這些輔助類可以很好的解決線程數量過
多時 Lock 鎖的頻繁操作。這三種輔助類為:
? CountDownLatch: 減少計數
? CyclicBarrier: 循環(huán)柵欄
? Semaphore: 信號燈
下面我們分別進行詳細的介紹和學習
7.1 減少計數 CountDownLatch
CountDownLatch 類可以設置一個計數器,然后通過 countDown 方法來進行
減 1 的操作,使用 await 方法等待計數器不大于 0,然后繼續(xù)執(zhí)行 await 方法
之后的語句。
? CountDownLatch 主要有兩個方法,當一個或多個線程調用 await 方法時,這
些線程會阻塞
? 其它線程調用 countDown 方法會將計數器減 1(調用 countDown 方法的線程
不會阻塞)
? 當計數器的值變?yōu)?0 時,因 await 方法阻塞的線程會被喚醒,繼續(xù)執(zhí)行
場景: 6 個同學陸續(xù)離開教室后值班同學才可以關門。
CountDownLatchDemo

package com.atguigu.test;
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatchDemo
*/
public class CountDownLatchDemo {
 /**
 * 6 個同學陸續(xù)離開教室后值班同學才可以關門
 * @param args
 */
 public static void main(String[] args) throws Exception{
 //定義一個數值為 6 的計數器
 CountDownLatch countDownLatch = new CountDownLatch(6);
 //創(chuàng)建 6 個同學
 for (int i = 1; i <= 6; i++) {
 new Thread(() ->{
 try{
 if(Thread.currentThread().getName().equals("同學 6")){
 Thread.sleep(2000);
 }
 System.out.println(Thread.currentThread().getName() + "離開了");
 //計數器減一,不會阻塞
 countDownLatch.countDown();
 }catch (Exception e){
 e.printStackTrace();
 }
 }, "同學" + i).start();
 }
 //主線程 await 休息
 System.out.println("主線程睡覺");
 countDownLatch.await();
 //全部離開后自動喚醒主線程
 System.out.println("全部離開了,現在的計數器為" + 
countDownLatch.getCount());
 }
}

7.2 循環(huán)柵欄 CyclicBarrier
CyclicBarrier 看英文單詞可以看出大概就是循環(huán)阻塞的意思,在使用中
CyclicBarrier 的構造方法第一個參數是目標障礙數,每次執(zhí)行 CyclicBarrier 一
次障礙數會加一,如果達到了目標障礙數,才會執(zhí)行 cyclicBarrier.await()之后
的語句??梢詫?CyclicBarrier 理解為加 1 操作
場景: 集齊 7 顆龍珠就可以召喚神龍
CyclicBarrierDemo

package com.atguigu.test;
import java.util.concurrent.CyclicBarrier;
/**
* CyclicBarrierDemo 案列
*/
public class CyclicBarrierDemo {
 //定義神龍召喚需要的龍珠總數
 private final static int NUMBER = 7;
 /**
 * 集齊 7 顆龍珠就可以召喚神龍
 * @param args
 */
 public static void main(String[] args) {
 //定義循環(huán)柵欄
 CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () ->{
 System.out.println("集齊" + NUMBER + "顆龍珠,現在召喚神龍!!!!!!!!!");
 });
 //定義 7 個線程分別去收集龍珠
 for (int i = 1; i <= 7; i++) {
 new Thread(()->{
 try {
 if(Thread.currentThread().getName().equals("龍珠 3 號")){
 System.out.println("龍珠 3 號搶奪戰(zhàn)開始,孫悟空開啟超級賽亞人模式!");
 Thread.sleep(5000);
 System.out.println("龍珠 3 號搶奪戰(zhàn)結束,孫悟空打贏了,拿到了龍珠 3!");
 }else{
 System.out.println(Thread.currentThread().getName() + "收集到
了!!!!");
 }
 cyclicBarrier.await();
 }catch (Exception e){
 e.printStackTrace();
 }
 }, "龍珠" + i + "號").start();
 }
 }
}

7.3 信號燈 Semaphore
Semaphore 的構造方法中傳入的第一個參數是最大信號量(可以看成最大線
程池),每個信號量初始化為一個最多只能分發(fā)一個許可證。使用 acquire 方
法獲得許可證,release 方法釋放許可
場景: 搶車位, 6 部汽車 3 個停車位
SemaphoreDemo

package com.atguigu.test;
import java.util.concurrent.Semaphore;
/**
* Semaphore 案列
*/
public class SemaphoreDemo {
 /**
 * 搶車位, 10 部汽車 1 個停車位
 * @param args
 */
 public static void main(String[] args) throws Exception{
 //定義 3 個停車位
 Semaphore semaphore = new Semaphore(1);
 //模擬 6 輛汽車停車
 for (int i = 1; i <= 10; i++) {
 Thread.sleep(100);
 //停車
 new Thread(() ->{
 try {
 System.out.println(Thread.currentThread().getName() + "找車位 ing");
 semaphore.acquire();
 System.out.println(Thread.currentThread().getName() + "汽車停車成
功!");
 Thread.sleep(10000);
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 System.out.println(Thread.currentThread().getName() + "溜了溜了");
 semaphore.release();
 }
 }, "汽車" + i).start();
 }
 }
}

8 讀寫鎖
8.1 讀寫鎖介紹
現實中有這樣一種場景:對共享資源有讀和寫的操作,且寫操作沒有讀操作那么頻繁。在沒有寫操作的時候,多個線程同時讀一個資源沒有任何問題,所以應該允許多個線程同時讀取共享資源;但是如果一個線程想去寫這些共享資源,就不應該允許其他線程對該資源進行讀和寫的操作了。
針對這種場景,JAVA 的并發(fā)包提供了讀寫鎖 ReentrantReadWriteLock,
它表示兩個鎖,一個是讀操作相關的鎖,稱為共享鎖;一個是寫相關的鎖,稱為排他鎖

  1. 線程進入讀鎖的前提條件:
    ? 沒有其他線程的寫鎖
    ? 沒有寫請求, 或者有寫請求,但調用線程和持有鎖的線程是同一個(可重入鎖)。
  2. 線程進入寫鎖的前提條件:
    ? 沒有其他線程的讀鎖
    ? 沒有其他線程的寫鎖
    而讀寫鎖有以下三個重要的特性:
    (1)公平選擇性:支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平。
    (2)重進入:讀鎖和寫鎖都支持線程重進入。
    (3)鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖。
8.3 入門案例 
場景: 使用 ReentrantReadWriteLock 對一個 hashmap 進行讀和寫操作
8.3.1 實現案例
//資源類
class MyCache {
 //創(chuàng)建 map 集合
 private volatile Map<String,Object> map = new HashMap<>();
 //創(chuàng)建讀寫鎖對象
 private ReadWriteLock rwLock = new ReentrantReadWriteLock();
 //放數據
 public void put(String key,Object value) {
 //添加寫鎖
 rwLock.writeLock().lock();
 try {
 System.out.println(Thread.currentThread().getName()+" 
"+key);
 //暫停一會
 TimeUnit.MICROSECONDS.sleep(300);
 //放數據
 map.put(key,value);
 System.out.println(Thread.currentThread().getName()+" 
"+key);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 //釋放寫鎖
 rwLock.writeLock().unlock();
 }
 }
 //取數據
 public Object get(String key) {
 //添加讀鎖
 rwLock.readLock().lock();
 Object result = null;
 try {
 System.out.println(Thread.currentThread().getName()+" 
"+key);
 //暫停一會
 TimeUnit.MICROSECONDS.sleep(300);
 result = map.get(key);
 System.out.println(Thread.currentThread().getName()+" 
"+key);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 //釋放讀鎖
 rwLock.readLock().unlock();
 }
 return result;
 }
}

8.4 小結(重要)
? 在線程持有讀鎖的情況下,該線程不能取得寫鎖(因為獲取寫鎖的時候,如果發(fā)現當前的讀鎖被占用,就馬上獲取失敗,不管讀鎖是不是被當前線程持有)。
? 在線程持有寫鎖的情況下,該線程可以繼續(xù)獲取讀鎖(獲取讀鎖時如果發(fā)現寫鎖被占用,只有寫鎖沒有被當前線程占用的情況才會獲取失敗)。
原因: 當線程獲取讀鎖的時候,可能有其他線程同時也在持有讀鎖,因此不能把獲取讀鎖的線程“升級”為寫鎖;而對于獲得寫鎖的線程,它一定獨占了讀寫鎖,因此可以繼續(xù)讓它獲取讀鎖,當它同時獲取了寫鎖和讀鎖后,還可以先釋放寫鎖繼續(xù)持有讀鎖,這樣一個寫鎖就“降級”為了讀鎖

9 阻塞隊列
9.1 BlockingQueue 簡介
Concurrent 包中,BlockingQueue 很好的解決了多線程中,如何高效安全
“傳輸”數據的問題。通過這些高效并且線程安全的隊列類,為我們快速搭建
高質量的多線程程序帶來極大的便利。本文詳細介紹了 BlockingQueue 家庭
中的所有成員,包括他們各自的功能以及常見使用場景。
阻塞隊列,顧名思義,首先它是一個隊列, 通過一個共享的隊列,可以使得數據
由隊列的一端輸入,從另外一端輸出;

當隊列是空的,從隊列中獲取元素的操作將會被阻塞
當隊列是滿的,從隊列中添加元素的操作將會被阻塞
試圖從空的隊列中獲取元素的線程將會被阻塞,直到其他線程往空的隊列插入新的元素
試圖向已滿的隊列中添加新元素的線程將會被阻塞,直到其他線程從隊列中移除一個或多
個元素或者完全清空,使隊列變得空閑起來并后續(xù)新增
常用的隊列主要有以下兩種:
? 先進先出(FIFO):先插入的隊列的元素也最先出隊列,類似于排隊的功能。
從某種程度上來說這種隊列也體現了一種公平性
? 后進先出(LIFO):后插入隊列的元素最先出隊列,這種隊列優(yōu)先處理最近發(fā)
生的事件(棧)
在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起
的線程又會自動被喚起
為什么需要 BlockingQueue
好處是我們不需要關心什么時候需要阻塞線程,什么時候需要喚醒線程,因為這一切BlockingQueue 都給你一手包辦了
在 concurrent 包發(fā)布以前,在多線程環(huán)境下,我們每個程序員都必須去自己控制這些細節(jié),尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的復雜度。多線程環(huán)境中,通過隊列可以很容易實現數據共享,比如經典的“生產者”和
“消費者”模型中,通過隊列可以很便利地實現兩者之間的數據共享。假設我們有若干生產者線程,另外又有若干個消費者線程。如果生產者線程需要把準備好的數據共享給消費者線程,利用隊列的方式來傳遞數據,就可以很方便地解決他們之間的數據共享問題。但如果生產者和消費者在某個時間段內,萬一發(fā)生數據處理速度不匹配的情況呢?理想情況下,如果生產者產出數據的速度大于消費者消費的速度,并且當生產出來的數據累積到一定程度的時候,那么生產者必須暫停等待一下(阻塞生產者線程),以便等待消費者線程把累積的數據處理完畢,反之亦然。
? 當隊列中沒有數據的情況下,消費者端的所有線程都會被自動阻塞(掛起),直到有數據放入隊列
? 當隊列中填滿數據的情況下,生產者端的所有線程都會被自動阻塞(掛起),直到隊列中有空的位置,線程被自動喚醒

9.2 BlockingQueue 核心方法
JUC 高并發(fā)編程基礎篇
BlockingQueue 的核心方法:
1.放入數據
? offer(anObject):表示如果可能的話,將 anObject 加到 BlockingQueue 里,即
如果 BlockingQueue 可以容納,則返回 true,否則返回 false.(本方法不阻塞當
前執(zhí)行方法的線程)
? offer(E o, long timeout, TimeUnit unit):可以設定等待的時間,如果在指定
的時間內,還不能往隊列中加入 BlockingQueue,則返回失敗
? put(anObject):把 anObject 加到 BlockingQueue 里,如果 BlockQueue 沒有
空間,則調用此方法的線程被阻斷直到 BlockingQueue 里面有空間再繼續(xù).
2.獲取數據
? poll(time): 取走 BlockingQueue 里排在首位的對象,若不能立即取出,則可以等
time 參數規(guī)定的時間,取不到時返回 null
? poll(long timeout, TimeUnit unit):從 BlockingQueue 取出一個隊首的對象,
如果在指定時間內,隊列一旦有數據可取,則立即返回隊列中的數據。否則知
道時間超時還沒有數據可取,返回失敗。
? take(): 取走 BlockingQueue 里排在首位的對象,若 BlockingQueue 為空,阻斷
進入等待狀態(tài)直到 BlockingQueue 有新的數據被加入;
? drainTo(): 一次性從 BlockingQueue 獲取所有可用的數據對象(還可以指定
獲取數據的個數),通過該方法,可以提升獲取數據效率;不需要多次分批加
鎖或釋放鎖。
9.3 入門案例

mport java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 阻塞隊列
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// List list = new ArrayList();
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.element());
//System.out.println(blockingQueue.add("x"));
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// 第二組
// System.out.println(blockingQueue.offer("a"));
// System.out.println(blockingQueue.offer("b"));
// System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("x"));
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// 第三組
// 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());
// 第四組
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));
}
}

10 ThreadPool 線程池
10.1 線程池簡介
線程池(英語:thread pool):一種線程使用模式。線程過多會帶來調度開銷,
進而影響緩存局部性和整體性能。而線程池維護著多個線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務。這避免了在處理短時間任務時創(chuàng)建與銷毀線程的代價。線程池不僅能夠保證內核的充分利用,還能防止過分調度。
例子: 10 年前單核 CPU 電腦,假的多線程,像馬戲團小丑玩多個球,CPU 需要來回切換。 現在是多核電腦,多個線程各自跑在獨立的 CPU 上,不用切換效率高。
線程池的優(yōu)勢: 線程池做的工作只要是控制運行的線程數量,處理過程中將任務放入隊列,然后在線程創(chuàng)建后啟動這些任務,如果線程數量超過了最大數量,超出數量的線程排隊等候,等其他線程執(zhí)行完畢,再從隊列中取出任務來執(zhí)行。它的主要特點為:
? 降低資源消耗: 通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的銷耗。
? 提高響應速度: 當任務到達時,任務可以不需要等待線程創(chuàng)建就能立即執(zhí)行。
? 提高線程的可管理性: 線程是稀缺資源,如果無限制的創(chuàng)建,不僅會銷耗系統資
源,還會降低系統的穩(wěn)定性,使用線程池可以進行統一的分配,調優(yōu)和監(jiān)控。
? Java 中的線程池是通過 Executor 框架實現的,該框架中用到了 Executor,Executors,
ExecutorService,ThreadPoolExecutor 這幾個類

JUC 高并發(fā)編程基礎篇
10.2 線程池參數說明
本次介紹 5 種類型的線程池
10.2.1 常用參數(重點)
? corePoolSize 線程池的核心線程數
? maximumPoolSize 能容納的最大線程數
? keepAliveTime 空閑線程存活時間
? unit 存活的時間單位
? workQueue 存放提交但未執(zhí)行任務的隊列
? threadFactory 創(chuàng)建線程的工廠類
? handler 等待隊列滿后的拒絕策略
線程池中,有三個重要的參數,決定影響了拒絕策略:corePoolSize - 核心線
程數,也即最小的線程數。workQueue - 阻塞隊列 。 maximumPoolSize -
最大線程數
當提交任務數大于 corePoolSize 的時候,會優(yōu)先將任務放到 workQueue 阻
塞隊列中。當阻塞隊列飽和后,會擴充線程池中線程數,直到達到
maximumPoolSize 最大線程數配置。此時,再多余的任務,則會觸發(fā)線程池
的拒絕策略了。
總結起來,也就是一句話,當提交的任務數大于(workQueue.size() +
maximumPoolSize ),就會觸發(fā)線程池的拒絕策略。

10.3 線程池的種類與創(chuàng)建

10.3.1 newCachedThreadPool(常用)
作用:創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程.
特點:
? 線程池中數量沒有固定,可達到最大值(Interger. MAX_VALUE)
? 線程池中的線程可進行緩存重復利用和回收(回收默認時間為 1 分鐘)
? 當線程池中,沒有可用線程,會重新創(chuàng)建一個線程
創(chuàng)建方式:

/**
* 可緩存線程池
* @return
*/
public static ExecutorService newCachedThreadPool(){
/**
* corePoolSize 線程池的核心線程數
* maximumPoolSize 能容納的最大線程數
* keepAliveTime 空閑線程存活時間
* unit 存活的時間單位
* workQueue 存放提交但未執(zhí)行任務的隊列
* threadFactory 創(chuàng)建線程的工廠類:可以省略
* handler 等待隊列滿后的拒絕策略:可以省略
*/
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}

場景: 適用于創(chuàng)建一個可無限擴大的線程池,服務器負載壓力較輕,執(zhí)行時間較短,任務多的場景
10.3.2 newFixedThreadPool(常用)
作用:創(chuàng)建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。在任意點,在大多數線程會處于處理任務的活動狀態(tài)。如果在所有線程處于活動狀態(tài)時提交附加任務,則在有可用線程之前,附加任務將在隊列中等待。如果在關閉前的執(zhí)行期間由于失敗而導致任何線程終止,那么一個新線程將代替它執(zhí)行后續(xù)的任務(如果需要)。在某個線程被顯式地關閉之前,池中的線程將一直存在。
特征:
? 線程池中的線程處于一定的量,可以很好的控制線程的并發(fā)量
? 線程可以重復被使用,在顯示關閉之前,都將一直存在
? 超出一定量的線程被提交時候需在隊列中等待
創(chuàng)建方式:

/**
* 固定長度線程池
* @return
*/
public static ExecutorService newFixedThreadPool(){
/**
* corePoolSize 線程池的核心線程數
* maximumPoolSize 能容納的最大線程數
* keepAliveTime 空閑線程存活時間
* unit 存活的時間單位
* workQueue 存放提交但未執(zhí)行任務的隊列
* threadFactory 創(chuàng)建線程的工廠類:可以省略
* handler 等待隊列滿后的拒絕策略:可以省略
*/
return new ThreadPoolExecutor(10,
10,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}

場景: 適用于可以預測線程數量的業(yè)務中,或者服務器負載較重,對線程數有嚴格限制的場景
10.3.3 newSingleThreadExecutor(常用)
作用:創(chuàng)建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。(注意,如果因為在關閉前的執(zhí)行期間出現失敗而終止了此單個線程,那么如果需要,一個新線程將代替它執(zhí)行后續(xù)的任務)。可保證順序地執(zhí)行各個任務,并且在任意給定的時間不會有多個線程是活動的。與其他等效的newFixedThreadPool 不同,可保證無需重新配置此方法所返回的執(zhí)行程序即可使用其他的線程。
特征: 線程池中最多執(zhí)行 1 個線程,之后提交的線程活動將會排在隊列中以此執(zhí)行
創(chuàng)建方式:

/**
* 單一線程池
* @return
*/
public static ExecutorService newSingleThreadExecutor(){
/**
* corePoolSize 線程池的核心線程數
* maximumPoolSize 能容納的最大線程數
* keepAliveTime 空閑線程存活時間
* unit 存活的時間單位
* workQueue 存放提交但未執(zhí)行任務的隊列
* threadFactory 創(chuàng)建線程的工廠類:可以省略
* handler 等待隊列滿后的拒絕策略:可以省略
*/
return new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}

場景: 適用于需要保證順序執(zhí)行各個任務,并且在任意時間點,不會同時有多個線程的場景
10.3.4 newScheduleThreadPool(了解)
作用: 線程池支持定時以及周期性執(zhí)行任務,創(chuàng)建一個 corePoolSize 為傳入參數,最大線程數為整形的最大數的線程池**
特征:
(1)線程池中具有指定數量的線程,即便是空線程也將保留 (2)可定時或者延遲執(zhí)行線程活動
創(chuàng)建方式:

public static ScheduledExecutorService newScheduledThreadPool(int
corePoolSize, 
ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, 
threadFactory);
}

場景: 適用于需要多個后臺線程執(zhí)行周期任務的場景

10.3.5 newWorkStealingPool
jdk1.8 提供的線程池,底層使用的是 ForkJoinPool 實現,創(chuàng)建一個擁有多個任務隊列的線程池,可以減少連接數,創(chuàng)建當前可用 cpu 核數的線程來并行執(zhí)行任務
創(chuàng)建方式:

public static ExecutorService newWorkStealingPool(int parallelism) {
/**
* parallelism:并行級別,通常默認為 JVM 可用的處理器個數
* factory:用于創(chuàng)建 ForkJoinPool 中使用的線程。
* handler:用于處理工作線程未處理的異常,默認為 null
* asyncMode:用于控制 WorkQueue 的工作模式:隊列---反隊列
*/
return new ForkJoinPool(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null,
true);
}

場景: 適用于大耗時,可并行執(zhí)行的場景
10.4 線程池入門案例

場景: 火車站 3 個售票口, 10 個用戶買票
package com.atguigu.test;
import java.util.concurrent.*;
/**
* 入門案例
*/
public class ThreadPoolDemo1 {
/**
* 火車站 3 個售票口, 10 個用戶買票
* @param args
*/
public static void main(String[] args) {
//定時線程次:線程數量為 3---窗口數為 3
ExecutorService threadService = new ThreadPoolExecutor(3,
3,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
//10 個人買票
for (int i = 1; i <= 10; i++) {
threadService.execute(()->{
try {
System.out.println(Thread.currentThread().getName() + "
窗口,開始賣票");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "
窗口買票結束");
}catch (Exception e){
e.printStackTrace();
}
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//完成后結束
threadService.shutdown();
}
}
}

12.1 CompletableFuture 簡介
CompletableFuture 在 Java 里面被用于異步編程,異步通常意味著非阻塞,
可以使得我們的任務單獨運行在與主線程分離的其他線程中,并且通過回調可
以在主線程中得到異步任務的執(zhí)行狀態(tài),是否完成,和是否異常等信息。
CompletableFuture 實現了 Future, CompletionStage 接口,實現了 Future
接口就可以兼容現在有線程池框架,而 CompletionStage 接口才是異步編程
的接口抽象,里面定義多種異步方法,通過這兩者集合,從而打造出了強大的
CompletableFuture 類。
12.2 Future 與 CompletableFuture
Futrue 在 Java 里面,通常用來表示一個異步任務的引用,比如我們將任務提交到線程池里面,然后我們會得到一個 Futrue,在 Future 里面有 isDone 方法來 判斷任務是否處理結束,還有 get 方法可以一直阻塞直到任務結束然后獲取結果,但整體來說這種方式,還是同步的,因為需要客戶端不斷阻塞等待或者不斷輪詢才能知道任務是否完成。
Future 的主要缺點如下:
(1)不支持手動完成
我提交了一個任務,但是執(zhí)行太慢了,我通過其他路徑已經獲取到了任務結果,現在沒法把這個任務結果通知到正在執(zhí)行的線程,所以必須主動取消或者一直等待它執(zhí)行完成
(2)不支持進一步的非阻塞調用
通過 Future 的 get 方法會一直阻塞到任務完成,但是想在獲取任務之后執(zhí)行額外的任務,因為 Future 不支持回調函數,所以無法實現這個功能
(3)不支持鏈式調用
對于 Future 的執(zhí)行結果,我們想繼續(xù)傳到下一個 Future 處理使用,從而形成一個鏈式的 pipline 調用,這在 Future 中是沒法實現的。
(4)不支持多個 Future 合并
比如我們有 10 個 Future 并行執(zhí)行,我們想在所有的 Future 運行完畢之后,執(zhí)行某些函數,是沒法通過 Future 實現的。
(5)不支持異常處理
Future 的 API 沒有任何的異常處理的 api,所以在異步運行時,如果出了問題是不好定位的。

CompletableFuture 入門使用 可詳細查看另一篇文章
Java 高級應用-多線程-(四)FutureTask的介紹及使用
文章來源地址http://www.zghlxwxcb.cn/news/detail-470742.html

到了這里,關于JUC 高并發(fā)編程基礎篇的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【JUC并發(fā)編程】

    【JUC并發(fā)編程】

    本筆記內容為狂神說JUC并發(fā)編程部分 目錄 一、什么是JUC 二、線程和進程 1、概述? 2、并發(fā)、并行 3、線程有幾個狀態(tài)? 4、wait/sleep 區(qū)別 三、Lock鎖(重點)? 四、生產者和消費者問題 五、八鎖現象 六、集合類不安全? 七、Callable ( 簡單 ) 八、常用的輔助類(必會) 1、CountDown

    2024年02月09日
    瀏覽(11)
  • JUC并發(fā)編程14 | ThreadLocal

    JUC并發(fā)編程14 | ThreadLocal

    尚硅谷JUC并發(fā)編程(100-111) ThreadLocal是什么? ThreadLocal 提供 線程局部變量 。這些變量與正常的變量有所不同,因為每一個線程在訪問ThreadLocal實例的時候(通過其get或set方法)都有自己的、獨立初始化的變量副本。ThreadLocal實例通常是類中的私有靜態(tài)字段,使用它的目的是

    2024年02月04日
    瀏覽(23)
  • JUC并發(fā)編程之原子類

    目錄 1. 什么是原子操作 1.1?原子類的作用 1.2?原子類的常見操作 原子類的使用注意事項 并發(fā)編程是現代計算機應用中不可或缺的一部分,而在并發(fā)編程中,處理共享資源的并發(fā)訪問是一個重要的問題。為了避免多線程訪問共享資源時出現競態(tài)條件(Race Condition)等問題,J

    2024年02月13日
    瀏覽(18)
  • 【JUC并發(fā)編程】讀寫鎖:ReadWriteLock

    【JUC并發(fā)編程】讀寫鎖:ReadWriteLock

    1. 不使用讀寫鎖 2. 使用讀寫鎖 ReadWriteLock讀寫鎖特點 ① 寫鎖是獨占鎖,一次只能被一個線程占有 ② 讀鎖是共享鎖,多個線程可以同時占有 讀-讀:可以共存 讀-寫:不能共存 寫-寫:不能共存

    2024年02月13日
    瀏覽(23)
  • JUC并發(fā)編程原理精講(源碼分析)

    JUC并發(fā)編程原理精講(源碼分析)

    JUC即 java.util.concurrent 涉及三個包: java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks 普通的線程代碼: Thread Runnable 沒有返回值、效率相比入 Callable 相對較低! Callable 有返回值!【工作常用】 進程 :是指一個內存中運行的程序,每個進程都有一個獨立的內存空間,

    2024年02月02日
    瀏覽(27)
  • JUC并發(fā)編程學習(十三)ForkJoin

    JUC并發(fā)編程學習(十三)ForkJoin

    什么是ForkJoin ForkJoin在JDK1.7,并發(fā)執(zhí)行任務!大數據量時提高效率。 大數據:Map Reduce(把大任務拆分成小任務) ForkJoin特點:工作竊取 為什么可以取竊取其他線程的任務呢?因為這里面維護的都是 雙端隊列 (即隊列的兩端都可以取元素) ForkJoin操作 在java.util.concurrent下的接

    2024年02月05日
    瀏覽(23)
  • JUC并發(fā)編程之AQS原理

    JUC并發(fā)編程之AQS原理

    全稱是 AbstractQueuedSynchronizer,是阻塞式鎖和相關的同步器工具的框架 特點: 用 state 屬性來表示資源的狀態(tài)(分獨占模式和共享模式),子類需要定義如何維護這個生態(tài),控制如何獲取鎖和釋放鎖 getState - 獲取 state 狀態(tài) setState - 設置 state 狀態(tài) compareAndSetState - cas 機制設置 s

    2023年04月18日
    瀏覽(30)
  • 【JUC并發(fā)編程】集合類安全問題

    【JUC并發(fā)編程】集合類安全問題

    代碼演示 上面代碼會報錯并發(fā)修改異?!癹ava.util.ConcurrentModificationException” 1. 使用Vector類【不推薦】 Vector類的add方法是同步方法,但是效率很低 代碼演示 2. 使用Collections工具類 使用 Collections.synchronizedList()方法 將普通的ArrayList類轉換為安全的集合類 代碼演示 3. 使用CopyOnW

    2024年02月12日
    瀏覽(17)
  • JUC并發(fā)編程之volatile詳解

    JUC并發(fā)編程之volatile詳解

    目錄 ? 1. volatile 1.1?volatile的作用 1.1.1?變量可見性 1.1.2?禁止指令重排序 1.2 volatile可見性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.5 volatile 日常使用場景 送書活動 ? 在并發(fā)編程中,多線程操作共享的變量時,可能會導致線程安全問題,如數據競爭、可見性

    2024年02月14日
    瀏覽(23)
  • JUC并發(fā)編程學習筆記(十五)JMM

    JUC并發(fā)編程學習筆記(十五)JMM

    請你談談對Volatile的理解 Volatile是java虛擬機提供的 輕量級的同步機制 1、保證可見性 2、不保證原子性 3、禁止指令重排 什么是JMM JVM-java虛擬機 JMM-java內存模型,不存在的東西,概念!約定 關于JMM的一些同步的約定: 線程解鎖前,必須把共享變量 立刻 刷回主存 線程加鎖前,

    2024年02月05日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包