1.前置知識
1.1.執行緒與行程
行程是一個具備獨立功能的程式的一次動態執行的程序,是作業系統進行資源分配和調度的獨立單位,
早期的作業系統是沒有執行緒這個概念的,行程就是擁有資源的運行的最小單位,也是程式運行的最小單位,隨著計算機的發展,行程的背景關系切換開銷較大
所以發明了一個新的概念“執行緒”,執行緒是程式執行中一個單一的順序執行流程,是程式執行的最小的單位,是處理器調度的分配的基本單位,
總結:
1.執行緒是執行的最小單位,行程是作業系統分配資源的最小單位
2.一個行程由一個執行緒或者多個執行緒組成,執行緒是一個行程中代碼的不同執行路線(多執行緒)
3.執行緒的調度和切換比行程快得多
行程可以類比為QQ.exe,而執行緒則是QQ的聊天功能,視頻功能的執行程序
1.2并行與并發
并行是指當系統有多個cpu的時候,多個執行緒可以在不同的cpu上同時執行,兩個行程互不搶占cpu資源,可以同時進行
并發是指在一個時間段中,有多個行程都處于已啟動到運行完畢的程序中,它們搶占cpu資源,cpu將一個時間段劃分成為一個個的時間片(時間區間),然后在這個時間片中進行行程的來回切換執行,
1.3執行緒的狀態
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;(終結)
}
2.Lock介面
2.1多執行緒編程模板
進行多執行緒編程的時候,在高內聚低耦合的情況下,撰寫執行緒 操作 資源類,例子代碼在下
class Ticket {/*資源類*/
private Lock lock = new ReentrantLock();/*可重入鎖*/
private int number = 300;
public void sale() {//操作(對外暴露的呼叫方法)
try {
lock.lock();
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "賣出第" + number-- + "張票,還剩下" + number + "張票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//執行緒
new Thread(() ->{ for (int i = 0; i < 300; i++) ticket.sale(); },"A").start();
new Thread(() ->{ for (int i = 0; i < 300; i++) ticket.sale(); },"B").start();
new Thread(() ->{ for (int i = 0; i < 300; i++) ticket.sale(); },"C").start();
}
}
2.2傳統Synchroinezd實作執行緒的通信
首先需要明白兩個概念:
1.等待池:當執行緒A呼叫某個物件的wait方法后,那么執行緒A就會釋放鎖并進入該物件的等待池,在等待池中的執行緒是不會去競爭鎖的,
2.鎖池:當某個執行緒呼叫該物件的notifyAll()方法時,處于該物件等待池中的所有物件都會進入鎖池,鎖池中的執行緒會去競爭鎖,
在執行緒通信的時候 撰寫代碼口訣 判斷 干活 通知
多執行緒互動中,必須防止多執行緒的虛假喚醒,也即(判斷只能用while,不能用if,根本原因是if之后,不會再進行判斷)
下方的例子中如果使用if判斷,當當前number值是1時,喚醒執行緒可能會喚醒執行add方法的執行緒,而且add方法的執行緒不會再執行判斷,所以會增加為2,導致錯誤
例子代碼實作對一個變數進行加一減一,
class AirConditioner{
private int number = 0;
public synchronized void add(){
//判斷(滿足條件跳出回圈)
while (number != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活(執行業務邏輯)
number ++;
System.out.println(Thread.currentThread().getName() + number);
//通知(喚醒其他執行緒)
this.notifyAll();
}
public synchronized void dec(){
//判斷
while (number == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活
number --;
System.out.println(Thread.currentThread().getName() + number);
//通知
this.notifyAll();
}
}
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner conditioner = new AirConditioner();
new Thread(()->{
for (int i = 0; i < 10; i++) {
conditioner.add();
}
},"同學A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
conditioner.dec();
}
},"同學B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
conditioner.add();
}
},"同學C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
conditioner.dec();
}
},"同學D").start();
}
}
2.3Lock實作執行緒的通信
首先看下圖

使用Lock后,synchronized中的wait和notify被await和signal方法代替了,
而下面代碼的Condition又是什么呢?還記得剛剛講的等待池和鎖池的概念嗎,他們有異曲同工之妙,但是Condition的功能更加強大,
class AirConditioner2 {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void add() {
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();
}
}
public void dec() {
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();
}
}
}
public class ThreadWaitNotifyDemo2 {
public static void main(String[] args) {
AirConditioner2 conditioner = new AirConditioner2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
conditioner.add();
}
}, "同學A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
conditioner.dec();
}
}, "同學B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
conditioner.add();
}
}, "同學C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
conditioner.dec();
}
}, "同學D").start();
}
}
2.4Lock實作精準喚醒執行緒
第一個問題:如何保證順序
第二個問題:如何保證喚醒的執行緒
解決:第一個問題使用一個標志位,定義一個變數來充當標志位,判斷時使用該變數來判斷屬于哪一個執行緒
第二個問題可以使用Condition來實作精準喚醒,condition都有自己的等待佇列,當await的時候,就將該執行緒放入自己的等待佇列中,當使用signal()的時候喚醒當前condition佇列中的執行緒
/* 多個執行緒之間按順序呼叫,實作A->B->C
* AA列印五次,BB列印十次,CC列印十五次
* 接著
* AA列印五次,BB列印十次,CC列印十五次
來十輪*/
class ShareResource{
private int number = 1;//A:1 B:2 C:3 標志位
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
/*設定三個condition是為了更加精確提升效率,每個condition都有自己的等待佇列,當await的時候,就將該執行緒放入自己的等待佇列中,
可以用當前condition.singal方法進行精確喚醒*/
public void print5(){
lock.lock();
try {
//判斷
while (number != 1){
condition1.await();
}
//干活
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//通知
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
//判斷
while (number != 2){
condition2.await();
}
//干活
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//通知
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try {
//判斷
while (number != 3){
condition3.await();
}
//干活
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//通知
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ZZ01ThreadOrderAccess {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
shareResource.print5();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
shareResource.print15();
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
shareResource.print10();
}
},"B").start();
}
}
總結
1.首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
2.synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
3.synchronized會自動釋放鎖(a 執行緒執行完同步代碼會釋放鎖 ;b 執行緒執行程序中發生例外會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成執行緒死鎖;
4.用synchronized關鍵字的兩個執行緒1和執行緒2,如果當前執行緒1獲得鎖,執行緒2執行緒等待,如果執行緒1阻塞,執行緒2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,執行緒可以不用一直等待就結束了;
5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)
6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題,
3.執行緒安全的集合類
3.1CopyOnWriteArrayList
先看看我們平時使用的ArrayList是如何在多執行緒環境報例外的
public static void listNotSafe1() {
//List<String> list = Collections.synchronizedList(new ArrayList<>());
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();
}
}
上述代碼執行程序中會出現java.util.ConcurrentModificationException(并發修改例外)

導致此例外的原因是什么呢?
看看報錯資訊是迭代器出現了例外
private class Itr implements Iterator<E> {
int cursor; //下一個要訪問元素的索引值
int lastRet = -1; //上一個訪問元素的索引值
int expectedModCount = modCount;//修改次數的期望值,modCount表示ArrayList被修改的次數
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
//當對list的次改次數不等于期望的修改次數后,就會產生這個例外,比如多執行緒擴容的情況下
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我們來看看執行緒安全的集合類
使用CopyOnWriteArrayList可以實作
public static void listNotSafe() {
//List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
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();
}
}
看看CopyOnWriteArrayList是如何實作并發修改的(保證高并發的讀,和寫的時候資料一致性),看看添加元素的原始碼

CopyOnWriteArrayList是一個寫時復制的思想,當向容器中添加元素的時候,并不會向原容器中直接添加,而是復制出一個新的陣列,將原有容器的元素添加到新的陣列
Object[] newElements 中,并且長度為原有容器長度+1,然后向新的容器添加元素,最后setArray(newElements),將原容器的參考指向新的容器,
這樣做的好處是,可以進行并發的讀,而不需要加鎖,只有對元素進行增刪時才會進行加鎖操作,所以CopyOnWriteArrayList也會一種讀寫分離的思想,讀和寫都是不同的容器
,
3.2CopyOnWriteArraySet
說句題外話,HashSet底層是HashMap,那么value值是什么呢?
value值是一個Object物件,為什么設定為Object,而不是null呢,null占用空間小,而且效率更高,究竟是為什么呢?
我們先來看看HashSet的remove方法
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
可以看到它呼叫的是HashMap的remove方法,來吧,看看HashMap的remove方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
看到這應該就明白了,當洗掉某一個資料時,會回傳被洗掉資料的value,如果HashSet的value設定為null的話,洗掉資料永遠回傳null,從而無法判斷是否洗掉成功,
CopyOnWriteArraySet又是如何把保證執行緒安全的呢?
看看它的構造方法
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
}
可以看到,它的底層也是使用的CopyOnWriteArrayList,接下來就不用多說了吧
3.3ConcurrentHashMap
請關注后續帖子
4.Callable介面
既然有了Runnable介面為什么還需要Callable介面呢?
來看看吧
class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("***********come in");
return 1024;
}
}
從上述代碼段可以看出run方法沒有回傳值,且不能拋出例外,而call方法有回傳值,而且能拋出例外,是否感覺很強大呢?
那么如何傳入Callable呢?看他的繼承樹,它和Runnable介面有沒有任何關系
我們可以先看看Runnable介面

它有一個實作類,是FutureTask,而FutureTask剛好可以傳入一個Callable,

好了,這下我們知道怎么寫代碼了
public class ZZ04CallableDemo {
public static void main(String[] args)
throws ExecutionException, InterruptedException {
MyThread2 thread2 = new MyThread2();
FutureTask futureTask = new FutureTask(thread2);
new Thread(futureTask,"a").start();
System.out.println(futureTask.get());
}
}
這下就可以傳入了,
那么Callable究竟是為了解決什么樣的問題而存在呢?
重點就是回傳值,可以獲取在不同的計算場景中獲取他們的回傳值,成功或失敗等
以上述代碼為例
當在主執行緒中執行比較耗時的任務時,又不想阻塞主執行緒,可以將這些任務交給Callable在后臺完成,當主執行緒將來需要時,就可以通過Future物件獲得后臺作業的計算結果或者執行狀態,
一般FutureTask多用于耗時的計算,主執行緒可以在完成自己的任務后,再去獲取結果,
重點:僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法,一旦計算完成, 就不能再重新開始或取消計算,get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態, 然后會回傳結果或者拋出例外,
也就是說呼叫get方法時,如果get方法未執行完畢,那么主執行緒也會被阻塞,所以說get方法最好是放在最后執行,
5.常用工具類
5.1CountDownLatch
計數器
例子代碼:教室里有6個學生,必須等6個學生全部離開教室,教室門才可以關閉
public class ZZ05CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i <6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"離開教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"班長關門走人");
}
}
CountDownLatch主要有兩個方法:
countDown():其它執行緒呼叫countDown方法會將計數器減1(呼叫countDown方法的執行緒不會阻塞)
await():當一個或多個執行緒呼叫await方法時,這些執行緒會阻塞,當計數器的值變為0時,因await方法阻塞的執行緒會被喚醒,繼續執行,
5.2CyclicBarrier
可回圈使用的屏障
例子代碼:當阻塞執行緒達到7個,才可以召喚神龍
public class ZZ06CycliBarrierDemo {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
{
System.out.println("召喚神龍");
};
});
for (int i = 0; i < 7; i++) {
final int tempInt = 1;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集到第" + tempInt + "顆龍珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
CyclicBarrier內部有一個方法
await():當前執行緒進入阻塞狀態(進入屏障)
只有當最后一個執行緒到達屏障點,匿名內部類的方法和所有被屏障攔截的執行緒才會繼續執行,
5.3Semaphore
信號量
例子代碼:6輛車搶占3個車位
public class ZZ07SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//模擬資源類,有三個空車位
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "搶占到了車位");
//暫停一會兒執行緒
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "離開了車位");
} catch (Exception e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
Semphore內部有兩個方法
acquire():獲取,當執行緒呼叫此方法時,要么成功獲取信號量(信號量-1),要么一直阻塞,直到有執行緒釋放信號量或者超時
release():釋放,當執行緒呼叫此方法時,會釋放信號量(信號量+1),然后喚醒阻塞的執行緒,
Semphore的使用主要有兩個目的,一個是設定多執行緒的互斥使用,一個是控制并發量
6.ReadWriteLock
在傳統的高并發情況下,不管讀和寫都會加鎖,其他執行緒只能等到獲取到鎖后才能去執行自己的操作,這樣的話如果只是讀取操作,也會導致效率變慢
所以誕生了讀寫鎖,只有讀操作時為了滿足高并發只是加共享鎖,此時讀操作允許多個執行緒同時訪問,當某一個執行緒拿到寫鎖時,此時會加排它鎖,除此執行緒外所有執行緒都不能讀寫
總而言之就是 讀-讀 可以共存 讀-寫 不可以共存 寫-寫 不可以共存
class Cache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "寫入資料"+ value );
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫入完成");
readWriteLock.writeLock().unlock();
}
public void get(String key) {
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "讀取了資料");
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "讀取完成" + o);
readWriteLock.readLock().unlock();
}
}
public class ZZ08ReadOnWriteLockDemo {
public static void main(String[] args) {
Cache cache = new Cache();
for (int i = 0; i < 5; i++) {
final int tempInt = i;
new Thread(() -> {
cache.put(tempInt+"",tempInt + "");
}, String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int tempInt = i;
new Thread(() -> {
cache.get(tempInt+"");
}, String.valueOf(i)).start();
}
}
}
底層原始碼請關注后續AQS解讀
7.BlockingQueue(阻塞佇列)
阻塞佇列是用于在高并發情況下不得不阻塞執行緒的時候,如何管理執行緒和資源的一種手段

Thread1向阻塞佇列中添加元素,Thread2從佇列中取走元素
當佇列是空的,從對列中取出元素的操作將會被阻塞
當佇列是滿的,向對列中添加元素的操作將會被阻塞
試圖從空的佇列中獲取元素的執行緒將會被阻塞,直到其他執行緒往空的佇列插入新的元素
試圖向已滿的佇列中添加新元素的執行緒將會被阻塞,直到其他執行緒從佇列中移除一個或多個元素或者完全清空,使佇列變得空閑起來并后續新增
阻塞佇列的優勢:
不需要關心什么時候需要阻塞執行緒,什么時候需要喚醒執行緒,因為一切都由BlockingQueue包辦了,
首先看看阻塞佇列的繼承樹

ArrayBlockQuque:由陣列結構組成的有界的阻塞佇列
LinkedBlockingQueue:由鏈表結構組成的有界(界限為Integer.MAX_VALUE)的阻塞佇列
SynchronousQueue:不存盤元素的阻塞佇列,也就是單個元素的佇列
LinkedBlockingDeque:由鏈表結構組成的有界(界限為Integer.MAX_VALUE)的雙向阻塞佇列
具體的方法見下

public class ZZ09BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue= new ArrayBlockingQueue<>(3);
/*此組add方法與remove,超出佇列界限會拋出例外
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
System.out.println(blockingQueue.add("d"));*/
/*System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.element());//回傳佇列第一個元素*/
/*此組offer與poll方法超出佇列界限不會拋出例外只回傳false和null
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());*/
/*put,當佇列沒有滿,可以往里添加,滿了后續的put操作會被阻塞*/
/*blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());*/
/* System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b", 3L, TimeUnit.SECONDS));*/
}
}
8.執行緒池
什么是執行緒池?
執行緒池是一種多執行緒處理任務的一種方式,處理程序中將任務添加到佇列(也就是我們上一講的阻塞佇列中),然后這些執行緒自動啟動這些任務
執行緒池的主要作業就是控制運行的執行緒數量,處理程序中將任務放入阻塞佇列,然后在執行緒創建后去執行這些任務,如果執行緒數量超過了最大數量,超出數量的執行緒排隊等候,等待其他執行緒執行完畢,再從佇列中取出任務來執行
主要特點:
執行緒復用
控制最大并發數
管理執行緒,
優勢:
1.重復利用已經創建的執行緒,降低執行緒創建和銷毀所帶來的開銷
2.任務可以不需要等待執行緒創建就可以執行,從而提高回應速度
3.提高執行緒的可管理性,使用執行緒池可以進行集中的監控,分配和調優,
看看Java中執行緒池的繼承樹

Executors類似于Collections,它作為執行緒池的工具類為我們提供了很多強大的功能
例子代碼如下:
public class ZZ11MyThreadPoolDemo {
public static void main(String[] args) {
/*
Executors 輔助工具類,類似于collections
* */
/* ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池五個執行緒,類似于銀行有五個受理視窗
ExecutorService threadPool = Executors.newSingleThreadExecutor();*///一個池子一個執行緒,類似于銀行有一個受理視窗
//一池子N個作業執行緒,類似于銀行有N個受理視窗,相當于前兩個執行緒池的結合體,
//執行很多任務時可以使用,執行緒池根據任務需要進行創建執行緒,但是在先前的執行緒可用的時候重用他們,在不可用的時候新建執行緒(擴容)
//ExecutorService threadPool = Executors.newCachedThreadPool();
//自定義執行緒池
ExecutorService threadPool = new ThreadPoolExecutor
(2, 5, 2L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
/*模擬十個顧客過來辦理業務,目前池子只有五個作業人員提供服務*/
for (int i = 0; i < 9; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "辦理業務");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
我們看看它們是如何創建執行緒池的
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
可以看到在原始碼中,ThreadPoolExecutor中有七個引數
int corePoolSize 常駐核心執行緒數
int maximumPoolSize 執行緒池中能容納同時執行的最大執行緒數
long keepAliveTime 多余空閑執行緒的存活時間,如果達到,執行緒會被銷毀
TimeUnit unit keepAliveTime的單位
BlockingQueue<Runnable> workQueue 任務佇列,被提交但是尚未被執行的任務
ThreadFactory threadFactory 創建執行緒的工廠
RejectedExecutionHandler handler 拒絕策略,當佇列滿了,并且作業執行緒大于等于執行緒池的最大執行緒數時如何來拒絕請求執行的runnable的策略
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/248547.html
標籤:其他
上一篇:RSA演算法密鑰長度的選擇
下一篇:網路增大
