線程與進程區(qū)別
每個正在系統(tǒng)上運行的程序都是一個進程。每個進程包含一到多個線程。線程是一組指令的集合,或者是程序的特殊段,它可以在程序里獨立執(zhí)行。也可以把它理解為代碼運行的上下文。所以線程基本上是輕量級的進程,它負責在單個程序里執(zhí)行多任務。通常由操作系統(tǒng)負責多個線程的調(diào)度和執(zhí)行。
使用線程可以把占據(jù)時間長的程序中的任務放到后臺去處理,程序的運行速度可能加快,在一些等待的任務實現(xiàn)上如用戶輸入、文件讀寫和網(wǎng)絡收發(fā)數(shù)據(jù)等,線程就比較有用了。在這種情況下可以釋放一些珍貴的資源如內(nèi)存占用等等。
如果有大量的線程,會影響性能,因為操作系統(tǒng)需要在它們之間切換,更多的線程需要更多的內(nèi)存空間,線程的中止需要考慮其對程序運行的影響。通常塊模型數(shù)據(jù)是在多個線程間共享的,需要防止線程死鎖情況的發(fā)生。
總結(jié):進程是所有線程的集合,每一個線程是進程中的一條執(zhí)行路徑。
創(chuàng)建線程5種方法:
1.Thread
2.Runnable
3.使用匿名內(nèi)部類
4.Callable
5.使用線程池創(chuàng)建線程
使用繼承Thread類還是使用實現(xiàn)Runnable接口好?
使用實現(xiàn)實現(xiàn)Runnable接口好,原因?qū)崿F(xiàn)了接口還可以繼續(xù)繼承,繼承了類不能再繼承。
常用線程api方法
start() 啟動線程
currentThread() 獲取當前線程對象
getID() 獲取當前線程ID Thread-編號 該編號從0開始
getName() 獲取當前線程名稱
sleep(long mill) 休眠線程
Stop() 停止線程,
常用線程構(gòu)造函數(shù)
Thread() 分配一個新的 Thread 對象
Thread(String name) 分配一個新的 Thread對象,具有指定的 name正如其名。
Thread(Runable r) 分配一個新的 Thread對象
Thread(Runable r, String name) 分配一個新的 Thread對象
守護線程
Java中有兩種線程,一種是用戶線程,另一種是守護線程。(主線程,gc線程,用戶線程)
用戶線程是指用戶自定義創(chuàng)建的線程,主線程停止,用戶線程不會停止。
守護線程當進程不存在或主線程停止,守護線程也會被停止。
使用setDaemon(true)方法設(shè)置為守護線程
多線程運行狀態(tài)
新建狀態(tài)、就緒狀態(tài)、運行狀態(tài)、阻塞狀態(tài)及死亡狀態(tài)。
1)新建狀態(tài)
當用new操作符創(chuàng)建一個線程時, 例如new Thread(r),線程還沒有開始運行,此時線程處在新建狀態(tài)。 當一個線程處于新生狀態(tài)時,程序還沒有開始運行線程中的代碼
2)就緒狀態(tài)
一個新創(chuàng)建的線程并不自動開始運行,要執(zhí)行線程,必須調(diào)用線程的start()方法。當線程對象調(diào)用start()方法即啟動了線程,start()方法創(chuàng)建線程運行的系統(tǒng)資源,并調(diào)度線程運行run()方法。當start()方法返回后,線程就處于就緒狀態(tài)。
處于就緒狀態(tài)的線程并不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運行線程。因為在單CPU的計算機系統(tǒng)中,不可能同時運行多個線程,一個時刻僅有一個線程處于運行狀態(tài)。因此此時可能有多個線程處于就緒狀態(tài)。對多個處于就緒狀態(tài)的線程是由Java運行時系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度的。
3)運行狀態(tài)
當線程獲得CPU時間后,它才進入運行狀態(tài),真正開始執(zhí)行run()方法.
4)阻塞狀態(tài)
線程運行過程中,可能由于各種原因進入阻塞狀態(tài):
1>線程通過調(diào)用sleep方法進入睡眠狀態(tài);
2>線程調(diào)用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調(diào)用者;
3>線程試圖得到一個鎖,而該鎖正被其他線程持有;
4>線程在等待某個觸發(fā)條件;
5)死亡狀態(tài)
有兩個原因會導致線程死亡:
1>run方法正常退出而自然死亡,
2>一個未捕獲的異常終止了run方法而使線程猝死。 為了確定線程在當前是否存活著(就是要么是可運行的,要么是被阻塞了),需要使用isAlive方法。如果是可運行或被阻塞,這個方法返回true; 如果線程仍舊是new狀態(tài)且不是可運行的, 或者線程死亡了,則返回false.
join()方法作用
join作用是讓其他線程變?yōu)榈却?,t1.join();//讓其他線程變?yōu)榈却?,直到當前t1線程執(zhí)行完畢,才釋放。
可以將兩個交替執(zhí)行的線程合并為順序執(zhí)行的線程。
比如在線程B中調(diào)用了線程A的Join()方法,直到線程A執(zhí)行完畢后,才會繼續(xù)執(zhí)行線程B。
需求:
創(chuàng)建一個線程,子線程執(zhí)行完畢后,主線程才能執(zhí)行。
class JoinThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---i:" + i);
}
}
}
public class JoinThreadDemo {
public static void main(String[] args) {
JoinThread joinThread = new JoinThread();
Thread t1 = new Thread(joinThread);
Thread t2 = new Thread(joinThread);
t1.start();
t2.start();
try {
//其他線程變?yōu)榈却隣顟B(tài),等t1線程執(zhí)行完成之后才能執(zhí)行join方法。
t1.join();
} catch (Exception e) {
}
for (int i = 0; i < 100; i++) {
System.out.println("main ---i:" + i);
}
}
}
yield()方法
Thread.yield()方法的作用:暫停當前正在執(zhí)行的線程,并執(zhí)行其他線程。(可能沒有效果)
yield()讓當前正在運行的線程回到可運行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運行的機會。因此,使用yield()的目的是讓具有相同優(yōu)先級的線程之間能夠適當?shù)妮啌Q執(zhí)行。但是,實際中無法保證yield()達到讓步的目的,因為,讓步的線程可能被線程調(diào)度程序再次選中。
結(jié)論:大多數(shù)情況下,yield()將導致線程從運行狀態(tài)轉(zhuǎn)到可運行狀態(tài),但有可能沒有效果。
為什么有線程安全問題?
當多個線程同時共享,同一個全局變量或靜態(tài)變量,做寫的操作時,可能會發(fā)生數(shù)據(jù)沖突問題,也就是線程安全問題。但是做讀操作是不會發(fā)生數(shù)據(jù)沖突問題。
案例:需求現(xiàn)在有100張火車票,有兩個窗口同時搶火車票,請使用多線程模擬搶票效果。
- 代碼:
class ThreadTrain1 implements Runnable {
private int count = 100;
private static Object oj = new Object();
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
sale();
}
}
public void sale() {
// 前提 多線程進行使用、多個線程只能拿到一把鎖。
// 保證只能讓一個線程 在執(zhí)行 缺點效率降低
// synchronized (oj) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
// }
}
}
public class ThreadDemo {
public static void main(String[] args) {
ThreadTrain1 threadTrain1 = new ThreadTrain1();
Thread t1 = new Thread(threadTrain1, "①號窗口");
Thread t2 = new Thread(threadTrain1, "②號窗口");
t1.start();
t2.start();
}
}
結(jié)論發(fā)現(xiàn),多個線程共享同一個全局成員變量時,做寫的操作可能會發(fā)生數(shù)據(jù)沖突問題。
線程安全解決辦法
問:如何解決多線程之間線程安全問題?
答:使用多線程之間同步synchronized或使用鎖(lock)。
問:為什么使用線程同步或使用鎖能解決線程安全問題呢?
答:將可能會發(fā)生數(shù)據(jù)沖突問題(線程不安全問題),只能讓當前一個線程進行執(zhí)行。代碼執(zhí)行完成后釋放鎖,然后才能讓其他線程進行執(zhí)行。這樣的話就可以解決線程不安全問題。
問:什么是多線程之間同步?
答:當多個線程共享同一個資源,不會受到其他線程的干擾。
同步代碼塊
- 什么是同步代碼塊?
就是將可能會發(fā)生線程安全問題的代碼,給包括起來。
synchronized(同一個數(shù)據(jù)){
可能會發(fā)生線程沖突問題
}
這就是同步代碼塊
定義:
synchronized(對象) //這個對象可以為任意對象
{
需要被同步的代碼
}
對象如同鎖,持有鎖的線程可以在同步中執(zhí)行 ,沒持有鎖的線程即使獲取CPU的執(zhí)行權(quán),也進不去 。
-
同步的前提:
1,必須要有兩個或者兩個以上的線程
2,必須是多個線程使用同一個鎖
3,必須保證同步中只能有一個線程在運行
好處:解決了多線程的安全問題
弊端:多個線程需要判斷鎖,較為消耗資源、搶鎖的資源。代碼樣例: private static Object oj = new Object(); public void sale() { // 前提 多線程進行使用、多個線程只能拿到一把鎖。 // 保證只能讓一個線程 在執(zhí)行 缺點效率降低 synchronized (oj) { if (count > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票"); count--; } } }
同步函數(shù)
- 同步函數(shù):在方法上修飾synchronized 稱為同步函數(shù)。
- 同步函數(shù)用的是什么鎖?為什么?
答:同步函數(shù)使用this鎖。
證明方式: 一個線程使用同步代碼塊(this明鎖),另一個線程使用同步函數(shù)。如果兩個線程搶票不能實現(xiàn)同步,那么會出現(xiàn)數(shù)據(jù)錯誤。 - 代碼:
class ThreadTrain2 implements Runnable {
private int count = 100;
public boolean flag = true;
private static Object oj = new Object();
@Override
public void run() {
if (flag) {
while (count > 0) {
synchronized (this) {
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
}
}
} else {
while (count > 0) {
sale();
}
}
}
public synchronized void sale() {
// 前提 多線程進行使用、多個線程只能拿到一把鎖。
// 保證只能讓一個線程 在執(zhí)行 缺點效率降低
// synchronized (oj) {
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
// }
}
}
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
ThreadTrain2 threadTrain1 = new ThreadTrain2();
Thread t1 = new Thread(threadTrain1, "①號窗口");
Thread t2 = new Thread(threadTrain1, "②號窗口");
t1.start();
Thread.sleep(40);
threadTrain1.flag = false;
t2.start();
}
}
靜態(tài)同步函數(shù)
靜態(tài)同步函數(shù):方法上加上static關(guān)鍵字,使用synchronized 關(guān)鍵字修飾,使用類.class文件作為鎖對象。
靜態(tài)的同步函數(shù)使用的鎖為該函數(shù)所屬字節(jié)碼文件對象,可以用 getClass方法獲取,也可以用當前 類名.class 表示。
代碼樣例:
synchronized (ThreadTrain.class) {
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
面試常問:
一個線程使用同步函數(shù),另一個線程使用同步代碼塊(this),能夠?qū)崿F(xiàn)同步。
一個線程使用同步函數(shù),另一個線程使用同步代碼塊(非this),不能實現(xiàn)同步。
一個線程使用同步函數(shù),另一個線程使用靜態(tài)同步函數(shù),不能實現(xiàn)同步。
總結(jié):
同步函數(shù)使用this鎖;
同步代碼塊可使用任意對象鎖或者this鎖;
靜態(tài)同步函數(shù)使用類的字節(jié)碼.class文件鎖。
多線程死鎖
多線程死鎖:同步中嵌套同步,導致鎖無法釋放
-
代碼:
class ThreadTrain6 implements Runnable { // 這是貨票總票數(shù),多個線程會同時共享資源 private int trainCount = 100; public boolean flag = true; private Object obj= new Object(); @Override public void run() { if (flag) { while (true) { synchronized (obj) { // 鎖(同步代碼塊)在什么時候釋放? 代碼執(zhí)行完, 自動釋放鎖. // 如果flag為true 先拿到 obj鎖,在拿到this 鎖、 才能執(zhí)行。 // 如果flag為false先拿到this,在拿到obj鎖,才能執(zhí)行。 // 死鎖解決辦法:不要在同步中嵌套同步。 sale(); } } } else { while (true) { sale(); } } } public synchronized void sale() { synchronized (obj) { if (trainCount > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票."); trainCount--; } } } } public class DeadlockThread { public static void main(String[] args) throws InterruptedException { ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個實例 Thread thread1 = new Thread(threadTrain, "一號窗口"); Thread thread2 = new Thread(threadTrain, "二號窗口"); thread1.start(); Thread.sleep(40); threadTrain.flag = false; thread2.start(); } }
原因分析:
線程一先拿到同步代碼塊的obj鎖,再拿到同步函數(shù)的this鎖;
線程一先拿到同步函數(shù)的this鎖,再拿到同步代碼塊的obj鎖,
每個線程都需要對方的鎖,但又互不讓鎖,就會導致死鎖。
多線程有三大特性
原子性、可見性、有序性
什么是原子性
即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。
一個很經(jīng)典的例子就是銀行賬戶轉(zhuǎn)賬問題: 比如從賬戶A向賬戶B轉(zhuǎn)1000元,那么必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。這2個操作必須要具備原子性才能保證不出現(xiàn)一些意外的問題。
我們操作數(shù)據(jù)也是如此,比如i = i+1;其中就包括,讀取i的值,計算i,寫入i。這行代碼在Java中是不具備原子性的,則多線程運行肯定會出問題,所以也需要我們使用同步和lock這些東西來確保這個特性了。
原子性其實就是保證數(shù)據(jù)一致、線程安全一部分,
什么是可見性
當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
若兩個線程在不同的cpu,那么線程1改變了i的值還沒刷新到主存,線程2又使用了i,那么這個i值肯定還是之前的,線程1對變量的修改線程沒看到這就是可見性問題。
什么是有序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
一般來說處理器為了提高程序運行效率,可能會對輸入代碼進行優(yōu)化,它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致,但是它會保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。如下:
int a = 10; //語句1
int r = 2; //語句2
a = a + 3; //語句3
r = a*a; //語句4
則因為重排序,他還可能執(zhí)行順序為 2-1-3-4,1-3-2-4 但絕不可能 2-1-4-3,因為這打破了依賴關(guān)系。 顯然重排序?qū)尉€程運行是不會有任何問題,而多線程就不一定了,所以我們在多線程編程時就得考慮這個問題了。
Java內(nèi)存模型
共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。

從上圖來看,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個步驟:
1. 首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去。
2. 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。
下面通過示意圖來說明這兩個步驟:

如上圖所示,本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本。假設(shè)初始時,這三個內(nèi)存中的x值都為0。線程A在執(zhí)行時,把更新后的x值(假設(shè)值為1)臨時存放在自己的本地內(nèi)存A中。當線程A和線程B需要通信時,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中,此時主內(nèi)存中的x值變?yōu)榱?。隨后,線程B到主內(nèi)存中去讀取線程A更新后的x值,此時線程B的本地內(nèi)存的x值也變?yōu)榱?。
從整體來看,這兩個步驟實質(zhì)上是線程A在向線程B發(fā)送消息,而且這個通信過程必須要經(jīng)過主內(nèi)存。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為java程序員提供內(nèi)存可見性保證。
總結(jié):
Java內(nèi)存模型:java內(nèi)存模型簡稱jmm,定義了一個線程對另一個線程可見。共享變量存放在主內(nèi)存中,每個線程都有自己的本地內(nèi)存,當多個線程同時訪問一個數(shù)據(jù)的時候,可能本地內(nèi)存沒有及時刷新到主內(nèi)存,所以就會發(fā)生線程安全問題。
Java內(nèi)存結(jié)構(gòu):是屬于jvm內(nèi)存分配,不要和Java內(nèi)存模型搞混。
volatile
volatile 關(guān)鍵字的作用是變量在多個線程之間可見。
- 代碼:
classThreadVolatileDemo extends Thread {
public boolean flag= true;
@Override
public void run() {
System.out.println("開始執(zhí)行子線程....");
while (flag) {
}
System.out.println("線程停止");
}
public void setRuning(boolean flag) {
this.flag= flag;
}
}
public class ThreadVolatile {
public static voidmain(String[] args) throws InterruptedException {
ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
threadVolatileDemo.start();
Thread.sleep(3000);
threadVolatileDemo.setRuning(false);
System.out.println("flag 已經(jīng)設(shè)置成false");
Thread.sleep(1000);
System.out.println(threadVolatileDemo.flag);
}
}
已經(jīng)將結(jié)果設(shè)置為fasle為什么?還一直在運行呢。
原因:線程之間是不可見的,讀取的是副本,沒有及時讀取到主內(nèi)存結(jié)果。
解決辦法:使用volatile關(guān)鍵字將解決線程之間可見性, 強制線程每次讀取該值的時候都去“主內(nèi)存”中取值。
Volatile非原子性
注意: Volatile非原子性
public class VolatileNoAtomic extends Thread {
private static volatile int count;
// private static AtomicInteger count = new AtomicInteger(0);
private static void addCount() {
for (int i = 0;i< 1000;i++) {
count++;
// count.incrementAndGet();
}
System.out.println(count);
}
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
for (int i = 0; i < 10; i++) {
arr[i] = new VolatileNoAtomic();
}
for (int i = 0; i < 10; i++) {
arr[i].start();
}
}
}
運行結(jié)果:

結(jié)果發(fā)現(xiàn) 數(shù)據(jù)不同步,因為Volatile不用具備原子性。
使用AtomicInteger原子類
AtomicInteger是一個提供原子操作的Integer類,通過線程安全的方式操作加減。(JDK1.5并發(fā)包中的類)
public class VolatileNoAtomic extends Thread {
static int count = 0;
private static AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//等同于i++
atomicInteger.incrementAndGet();
}
System.out.println(count);
}
public static void main(String[] args) {
// 初始化10個線程
VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
for (int i = 0; i < 10; i++) {
// 創(chuàng)建
volatileNoAtomic[i] = new VolatileNoAtomic();
}
for (int i = 0; i < volatileNoAtomic.length; i++) {
volatileNoAtomic[i].start();
}
}
}
volatile與synchronized區(qū)別
僅靠volatile不能保證線程的安全性。(原子性)
①volatile輕量級,只能修飾變量。
synchronized重量級,還可修飾方法
②volatile只能保證數(shù)據(jù)的可見性,不能用來同步,因為多個線程并發(fā)訪問volatile修飾的變量不會阻塞。
synchronized不僅保證可見性,而且還保證原子性(數(shù)據(jù)一致),因為,只有獲得了鎖的線程才能進入臨界區(qū),從而保證臨界區(qū)中的所有語句都全部執(zhí)行。多個線程爭搶synchronized鎖對象時,會出現(xiàn)阻塞。
③線程安全性
線程安全性包括兩個方面:1)可見性,2)原子性。
從上面自增的例子中可以看出:僅僅使用volatile并不能保證線程安全性。而synchronized則可實現(xiàn)線程的安全性。
什么是多線程之間通訊
多線程之間通訊,其實就是多個線程在操作同一個資源,但是操作的動作不同。
多線程之間通訊需求:
第一個線程寫入(input)用戶,另一個線程取讀取(out)用戶。實現(xiàn)讀一個,寫一個操作。
畫圖演示:

代碼實現(xiàn):
- 共享資源源實體類
class Res {
public String userSex;
public String userName;
}
- 輸入線程資源
class IntThrad extends Thread {
private Res res;
public IntThrad(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
if (count == 0) {
res.userName = "余勝軍";
res.userSex = "男";
} else {
res.userName = "小紅";
res.userSex = "女";
}
count = (count + 1) % 2;
}
}
}
- 輸出線程
class OutThread extends Thread {
private Res res;
public OutThread(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
System.out.println(res.userName + "--" + res.userSex);
}
}
}
- 運行代碼
public class ThreadDemo01 {
public static void main(String[] args) {
Res res = new Res();
InputThread inputThread = new InputThread(res);
OutThrad outThrad = new OutThrad(res);
inputThread.start();
outThrad.start();
}
}
-
運行結(jié)果
注意:數(shù)據(jù)發(fā)生錯亂,造成線程安全問題(消費者可能還沒讀,生產(chǎn)者就已經(jīng)修改了)
- 解決線程安全問題
1)如果使用volatile,只能保證數(shù)據(jù)的可見性,不能保證原子性。
2)如果使用synchronized,雖然可以保證原子性,但是消息會被重復生產(chǎn)與消費。
如圖所示:
輸入線程加上synchronized
class IntThrad extends Thread {
private Res res;
public IntThrad(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (count == 0) {
res.userName = "余勝軍";
res.userSex = "男";
} else {
res.userName = "小紅";
res.userSex = "女";
}
count = (count + 1) % 2;
}
}
}
}
輸出線程加上synchronized
class Res {
public String userName;
public String sex;
}
class InputThread extends Thread {
private Res res;
public InputThread(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (count == 0) {
res.userName = "余勝軍";
res.sex = "男";
} else {
res.userName = "小紅";
res.sex = "女";
}
count = (count + 1) % 2;
}
}
}
}
class OutThrad extends Thread {
private Res res;
public OutThrad(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
System.out.println(res.userName + "," + res.sex);
}
}
}
}
public class ThreadDemo01 {
public static void main(String[] args) {
Res res = new Res();
InputThread inputThread = new InputThread(res);
OutThrad outThrad = new OutThrad(res);
inputThread.start();
outThrad.start();
}
}
解決方法:
要同時滿足以下三點
1)生產(chǎn)線程生產(chǎn)一個,消費線程立馬消費
2)當生產(chǎn)者沒用任何生產(chǎn),消費者不能進行讀
3)當消費者沒有消費完,生產(chǎn)者不能再繼續(xù)生產(chǎn)
wait()、notify、notifyAll()方法
wait()、notify()、notifyAll()是三個定義在Object類里的方法,可以用來控制線程的狀態(tài)。
這三個方法最終調(diào)用的都是jvm級的native方法,隨著jvm運行平臺的不同可能有些許差異。
如果對象調(diào)用了wait方法就會使持有該對象的線程把該對象的控制權(quán)交出去,然后處于等待狀態(tài)。
如果對象調(diào)用了notify方法就會通知某個正在等待這個對象的控制權(quán)的線程可以繼續(xù)運行。
如果對象調(diào)用了notifyAll方法就會通知所有等待這個對象控制權(quán)的線程繼續(xù)運行。
注意:一定要在線程同步中使用,并且是同一個鎖的資源。
class Res {
public String userSex;
public String userName;
//線程通訊標識
public boolean flag = false;
}
class IntThrad extends Thread {
private Res res;
public IntThrad(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (res.flag) {
try {
// 當前線程變?yōu)榈却?,但是可以釋放鎖
res.wait();
} catch (Exception e) {
}
}
if (count == 0) {
res.userName = "余勝軍";
res.userSex = "男";
} else {
res.userName = "小紅";
res.userSex = "女";
}
count = (count + 1) % 2;
res.flag = true;
// 喚醒當前線程
res.notify();
}
}
}
}
class OutThread extends Thread {
private Res res;
public OutThread(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
if (!res.flag) {
try {
res.wait();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(res.userName + "--" + res.userSex);
res.flag = false;
res.notify();
}
}
}
}
public class ThreaCommun {
public static void main(String[] args) {
Res res = new Res();
IntThrad intThrad = new IntThrad(res);
OutThread outThread = new OutThread(res);
intThrad.start();
outThread.start();
}
}
wait與sleep區(qū)別
1)對于sleep()方法,我們首先要知道該方法是屬于Thread類中的。而wait()方法,則是屬于Object類中的。
2)sleep()方法導致了程序暫停執(zhí)行指定的時間,讓出cpu該其他線程,但是他的監(jiān)控狀態(tài)依然保持者,當指定的時間到了又會自動恢復運行狀態(tài)。在調(diào)用sleep()方法的過程中,線程不會釋放對象鎖。
而當調(diào)用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調(diào)用notify()方法后本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態(tài)。
JDK1.5-Lock
在 jdk1.5 之后,并發(fā)包中新增了 Lock 接口(以及相關(guān)實現(xiàn)類)用來實現(xiàn)鎖功能,Lock 接口提供了與 synchronized 關(guān)鍵字類似的同步功能,但需要在使用時手動獲取鎖和釋放鎖。
- Lock寫法
Lock lock = new ReentrantLock();
lock.lock();
try{
//可能會出現(xiàn)線程安全的操作
}finally{
//一定在finally中釋放鎖
//也不能把獲取鎖在try中進行,因為有可能在獲取鎖的時候拋出異常
lock.unlock();
}
Lock 接口與 synchronized 關(guān)鍵字的區(qū)別
Lock 接口可以嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖。如果這一時刻鎖沒有被其他線程獲取到,則成功獲取并持有鎖。
Lock 接口能被中斷地獲取鎖 與 synchronized 不同,獲取到鎖的線程能夠響應中斷,當獲取到的鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。
Lock 接口在指定的截止時間之前獲取鎖,如果截止時間到了依舊無法獲取鎖,則返回。
Condition用法
Condition的功能類似于在傳統(tǒng)的線程技術(shù)中的,Object.wait()和Object.notify()的功能。
- 用法
Condition condition = lock.newCondition();
condition.await(); //類似wait
condition. signal(); //類似notify
- 代碼
class Res {
public String userName;
public String sex;
public boolean flag = false;
Lock lock = new ReentrantLock();
}
class InputThread extends Thread {
private Res res;
Condition newCondition;
public InputThread(Res res, Condition newCondition) {
this.res = res;
this.newCondition=newCondition;
}
@Override
public void run() {
int count = 0;
while (true) {
// synchronized (res) {
try {
res.lock.lock();
if (res.flag) {
try {
// res.wait();
newCondition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
if (count == 0) {
res.userName = "余勝軍";
res.sex = "男";
} else {
res.userName = "小紅";
res.sex = "女";
}
count = (count + 1) % 2;
res.flag = true;
// res.notify();
newCondition.signal();
} catch (Exception e) {
// TODO: handle exception
}finally {
res.lock.unlock();
}
}
// }
}
}
class OutThrad extends Thread {
private Res res;
private Condition newCondition;
public OutThrad(Res res,Condition newCondition) {
this.res = res;
this.newCondition=newCondition;
}
@Override
public void run() {
while (true) {
// synchronized (res) {
try {
res.lock.lock();
if (!res.flag) {
try {
// res.wait();
newCondition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(res.userName + "," + res.sex);
res.flag = false;
// res.notify();
newCondition.signal();
} catch (Exception e) {
// TODO: handle exception
}finally {
res.lock.unlock();
}
// }
}
}
}
public class ThreadDemo01 {
public static void main(String[] args) {
Res res = new Res();
Condition newCondition = res.lock.newCondition();
InputThread inputThread = new InputThread(res,newCondition);
OutThrad outThrad = new OutThrad(res,newCondition);
inputThread.start();
outThrad.start();
}
}
如何停止線程?
停止線程思路
- 使用退出標志,使線程正常退出,也就是當run方法完成后線程終止。
- 使用stop方法強行終止線程(這個方法不推薦使用,因為stop和suspend、resume一樣,也可能發(fā)生不可預料的結(jié)果)。
- 使用interrupt方法中斷線程。
什么是ThreadLocal
ThreadLocal提供一個線程的局部變量,訪問某個線程擁有自己局部變量。
當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
ThreadLocal的接口方法
void set(Object value)設(shè)置當前線程的線程局部變量的值。
public Object get()該方法返回當前線程所對應的線程局部變量。
public void remove()將當前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 1.5新增的方法。需要指出的是,當線程結(jié)束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。
protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設(shè)計的。這個方法是一個延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實現(xiàn)直接返回一個null。案例:創(chuàng)建三個線程,每個線程生成自己獨立序列號。
代碼:
class Res {
// 生成序列號共享變量
public static Integer count = 0;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
};
};
public Integer getNum() {
int count = threadLocal.get() + 1;
threadLocal.set(count);
return count;
}
}
public class ThreadLocaDemo2 extends Thread {
private Res res;
public ThreadLocaDemo2(Res res) {
this.res = res;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
}
}
public static void main(String[] args) {
Res res = new Res();
ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
threadLocaDemo1.start();
threadLocaDemo2.start();
threadLocaDemo3.start();
}
}
ThreadLocal實現(xiàn)原理
ThreadLocal通過map集合,
map.put(“當前線程”,值);
map.get(“當前線程”);
練習題
設(shè)計4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1。
public class MultiThread {
private int j;
public static void main(String[] args) {
MultiThread mt = new MultiThread();
Inc inc = mt.new Inc();
Dec dec = mt.new Dec();
// 4個線程(0、1、2、3)
for (int i = 0; i < 2; i++) {
Thread t = new Thread(inc);
t.start();
t = new Thread(dec);
t.start();
}
// System.exit(0);// 如果報錯加上此句
}
// 對j增加1的方法
private synchronized void inc() {
j++;
System.out.println(Thread.currentThread().getName() + "-inc:" + j);
}
// 對j減少1的方法
private synchronized void dec() {
j--;
System.out.println(Thread.currentThread().getName() + "-dec:" + j);
}
// 內(nèi)部類實現(xiàn)線程
class Inc implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
inc();
}
}
}
class Dec implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
dec();
}
}
}
}
同步容器類:
Vector與ArrayList區(qū)別
1.ArrayList是最常用的List實現(xiàn)類,內(nèi)部是通過數(shù)組實現(xiàn)的,它允許對元素進行快速隨機訪問。數(shù)組的缺點是每個元素之間不能有間隔,當數(shù)組大小不滿足時需要增加存儲能力,就要將已經(jīng)有數(shù)組的數(shù)據(jù)復制到新的存儲空間中。當從ArrayList的中間位置插入或者刪除元素時,需要對數(shù)組進行復制、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。
2.Vector與ArrayList一樣,也是通過數(shù)組實現(xiàn)的,不同的是它支持線程的同步,即某一時刻只有一個線程能夠?qū)慥ector,避免多線程同時寫而引起的不一致性,但實現(xiàn)同步需要很高的花費,因此,訪問它比訪問ArrayList慢。
注意: Vector線程安全,底層對方法加上synchronized關(guān)鍵字、ArrayList線程不安全,底層方法未加同步關(guān)鍵字。
Vector源碼類——Add方法源碼類

ArrayList源碼——Add方法源碼

HashTable與HashMap
1.HashMap不是線程安全的
HashMap是一個接口,是Map接口的子接口,是將鍵映射到值的對象,其中鍵和值都是對象,并且不能包含重復鍵,但可以包含重復值。HashMap允許null key和null value,而hashtable不允許。
2.HashTable是線程安全的一個Collection。
3.HashMap是Hashtable的輕量級實現(xiàn)(非線程安全的實現(xiàn)),他們都完成了Map接口,主要區(qū)別在于HashMap允許空(null)鍵值(key),由于非線程安全,效率上可能高于HashTable。
HashMap允許將null作為一個entry的key或者value,而HashTable不允許。
HashMap把HashTable的contains方法去掉了,改成containsValue和containsKey。
注意: HashTable線程安全,HashMap線程不安全。
源碼分析:原理同Vector與ArrayList
synchronizedMap
Collections.synchronizedMap(hashMap); 將線程不安全的集合變?yōu)榫€程安全集合。
源碼分析:
ConcurrentHashMap
ConcurrentMap接口下有兩個重要的實現(xiàn) :
1)ConcurrentHashMap
2)ConcurrentskipListMap (支持并發(fā)排序功能。彌補ConcurrentHashMap)
ConcurrentHashMap內(nèi)部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的HashTable,它們有自己的鎖。
只要多個修改操作發(fā)生在不同的段上,它們就可以并發(fā)進行。
把一個整體分成了16個段Segment,也就是最高支持16個線程的并發(fā)修改操作。
這也是在多線程場景時減小鎖的粒度從而降低鎖競爭的一種方案。
并且代碼中大多共享變量使用volatile關(guān)鍵字聲明,目的是第一時間獲取修改的內(nèi)容,性能非常好。
CountDownLatch
CountDownLatch類位于java.util.concurrent包下,利用它可以實現(xiàn)類似計數(shù)器的功能。
CountDownLatch結(jié)果為0, 阻塞變?yōu)檫\行狀態(tài)。
比如有一個任務A,它要等待其他4個任務執(zhí)行完畢之后才能執(zhí)行,此時就可以利用CountDownLatch來實現(xiàn)這種功能了。
public class Test002 {
public static void main(String[] args) throws InterruptedException {
System.out.println("等待子線程執(zhí)行完畢...");
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程," + Thread.currentThread().getName() + "開始執(zhí)行...");
countDownLatch.countDown();// 每次減去1
System.out.println("子線程," + Thread.currentThread().getName() + "結(jié)束執(zhí)行...");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程," + Thread.currentThread().getName() + "開始執(zhí)行...");
countDownLatch.countDown();
System.out.println("子線程," + Thread.currentThread().getName() + "結(jié)束執(zhí)行...");
}
}).start();
countDownLatch.await();// 調(diào)用當前方法主線程阻塞 countDown結(jié)果為0, 阻塞變?yōu)檫\行狀態(tài)
System.out.println("兩個子線程執(zhí)行完畢....");
System.out.println("繼續(xù)主線程執(zhí)行..");
}
}
CyclicBarrier
CyclicBarrier初始化時規(guī)定一個數(shù)目,然后計算調(diào)用了CyclicBarrier.await()進入等待的線程數(shù)。當線程數(shù)達到了這個數(shù)目時,所有進入等待狀態(tài)的線程被喚醒并繼續(xù)。
CyclicBarrier就象它名字的意思一樣,可看成是個障礙, 所有的線程必須到齊后才能一起通過這個障礙。
CyclicBarrier初始時還可帶一個Runnable的參數(shù), 此Runnable任務在CyclicBarrier的數(shù)目達到后,所有其它線程被喚醒前被執(zhí)行。
class Writer extends Thread {
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier){
this.cyclicBarrier=cyclicBarrier;
}
@Override
public void run() {
System.out.println("線程" + Thread.currentThread().getName() + ",正在寫入數(shù)據(jù)");
try {
Thread.sleep(3000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("線程" + Thread.currentThread().getName() + ",寫入數(shù)據(jù)成功.....");
try {
cyclicBarrier.await();
} catch (Exception e) {
}
System.out.println("所有線程執(zhí)行完畢..........");
}
}
public class Test001 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(5);
for (int i = 0; i < 5; i++) {
Writer writer = new Writer(cyclicBarrier);
writer.start();
}
}
}
Semaphore
Semaphore是一種基于計數(shù)的信號量。它可以設(shè)定一個閾值,基于此,多個線程競爭獲取許可信號,做自己的申請后歸還,超過閾值后,線程申請許可信號將會被阻塞。Semaphore可以用來構(gòu)建一些對象池,資源池之類的,比如數(shù)據(jù)庫連接池,我們也可以創(chuàng)建計數(shù)為1的Semaphore,將其作為一種類似互斥鎖的機制,這也叫二元信號量,表示兩種互斥狀態(tài)。它的用法如下:
wc.availablePermits(); //用來獲取當前可用的資源數(shù)量
wc.acquire(); //申請資源
wc.release();// 釋放資源
-
代碼結(jié)構(gòu)
// 創(chuàng)建一個計數(shù)閾值為5的信號量對象 // 只能5個線程同時訪問 Semaphore semp = new Semaphore(5); try { // 申請許可 semp.acquire(); try { // 業(yè)務邏輯 } catch (Exception e) { } finally { // 釋放許可 semp.release(); } } catch (InterruptedException e) { }
案例:
需求: 一個廁所只有3個坑位,但是有10個人來上廁所,那怎么辦?假設(shè)10的人的編號分別為1-10,并且1號先到廁所,10號最后到廁所。那么1-3號來的時候必然有可用坑位,順利如廁,4號來的時候需要看看前面3人是否有人出來了,如果有人出來,進去,否則等待。同樣的道理,4-10號也需要等待正在上廁所的人出來后才能進去,并且誰先進去這得看等待的人是否有素質(zhì),是否能遵守先來先上的規(guī)則。
- 代碼:
class Parent implements Runnable {
private String name;
private Semaphore wc;
public Parent(String name,Semaphore wc){
this.name=name;
this.wc=wc;
}
@Override
public void run() {
try {
// 剩下的資源(剩下的茅坑)
int availablePermits = wc.availablePermits();
if (availablePermits > 0) {
System.out.println(name+"天主我也,我有茅坑了...");
} else {
System.out.println(name+"怎么沒有茅坑了...");
}
//申請茅坑 如果資源達到3次,就等待
wc.acquire();
System.out.println(name+"終于輪我上廁所了..爽啊");
Thread.sleep(new Random().nextInt(1000)); // 模擬上廁所時間。
System.out.println(name+"廁所上完了...");
} catch (Exception e) {
}finally{
wc.release();
}
}
}
public class TestSemaphore02 {
public static void main(String[] args) {
// 一個廁所只有3個坑位,但是有10個人來上廁所,那怎么辦?假設(shè)10的人的編號分別為1-10,并且1號先到廁所,10號最后到廁所。那么1-3號來的時候必然有可用坑位,順利如廁,4號來的時候需要看看前面3人是否有人出來了,如果有人出來,進去,否則等待。同樣的道理,4-10號也需要等待正在上廁所的人出來后才能進去,并且誰先進去這得看等待的人是否有素質(zhì),是否能遵守先來先上的規(guī)則。
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=10; i++) {
Parent parent = new Parent("第"+i+"個人,",semaphore);
new Thread(parent).start();
}
}
}


并發(fā)隊列:
在并發(fā)隊列上JDK提供了兩套實現(xiàn):
一個是以ConcurrentLinkedQueue為代表的高性能隊列,
一個是以BlockingQueue接口為代表的阻塞隊列,
無論哪種都繼承自Queue。
ConcurrentLinkedDeque
ConcurrentLinkedQueue是一個適用于高并發(fā)場景下的隊列,通過無鎖的方式,實現(xiàn)了高并發(fā)狀態(tài)下的高性能。通常ConcurrentLinkedQueue性能好于BlockingQueue。它是一個基于鏈接節(jié)點的無界線程安全隊列,該隊列的元素遵循先進先出的原則。頭是最先加入的,尾是最近加入的,該隊列不允許null元素。
ConcurrentLinkedQueue重要方法:
add() 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這兩個方法沒有任何區(qū)別)
poll() 和peek() 都是取頭元素節(jié)點,區(qū)別在于前者會刪除元素(出隊列),后者不會。
- 代碼示例:
ConcurrentLinkedDeque q = new ConcurrentLinkedDeque();
//入隊列
q.offer("張三");
q.offer("李四");
//獲取總長度
System.out.println(q.size());
//從頭獲取元素,刪除該元素(出隊列)
System.out.println(q.poll());
//從頭獲取元素,不刪除該元素
System.out.println(q.peek());
//獲取總長度
System.out.println(q.size());
- 輸出:
2
張三
李四
1
BlockingQueue
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。
這兩個附加的操作是:
在隊列為空時,獲取元素的線程會等待隊列變?yōu)榉强铡?br>
當隊列滿時,存儲元素的線程會等待隊列可用。
阻塞隊列常用于生產(chǎn)者和消費者的場景,生產(chǎn)者是往隊列里添加元素的線程,消費者是從隊列里拿元素的線程。阻塞隊列就是生產(chǎn)者存放元素的容器,而消費者也只從容器里拿元素。
BlockingQueue即阻塞隊列,從阻塞這個詞可以看出,在某些情況下對阻塞隊列的訪問可能會造成阻塞。
被阻塞的情況主要有如下兩種:
1)當隊列滿了的時候進行入隊列操作
2)當隊列空了的時候進行出隊列操作
因此,當一個線程試圖對一個已經(jīng)滿了的隊列進行入隊列操作時,它將會被阻塞,除非有另一個線程做了出隊列操作;同樣,當一個線程試圖對一個空隊列進行出隊列操作時,它將會被阻塞,除非有另一個線程進行了入隊列操作。
在Java中,BlockingQueue的接口位于java.util.concurrent 包中(在Java5版本開始提供),由上面介紹的阻塞隊列的特性可知,阻塞隊列是線程安全的。
在新增的Concurrent包中,BlockingQueue很好的解決了多線程中,如何高效安全“傳輸”數(shù)據(jù)的問題。通過這些高效并且線程安全的隊列類,為我們快速搭建高質(zhì)量的多線程程序帶來極大的便利。
常用的隊列主要有以下兩種:(當然通過不同的實現(xiàn)方式,還可以延伸出很多不同類型的隊列,DelayQueue就是其中的一種)
1)先進先出(FIFO):先插入的隊列的元素也最先出隊列,類似于排隊的功能。從某種程度上來說這種隊列也體現(xiàn)了一種公平性。
2)后進先出(LIFO):后插入隊列的元素最先出隊列,這種隊列優(yōu)先處理最近發(fā)生的事件。
多線程環(huán)境中,通過隊列可以很容易實現(xiàn)數(shù)據(jù)共享,比如經(jīng)典的“生產(chǎn)者”和“消費者”模型中,通過隊列可以很便利地實現(xiàn)兩者之間的數(shù)據(jù)共享。假設(shè)我們有若干生產(chǎn)者線程,另外又有若干個消費者線程。如果生產(chǎn)者線程需要把準備好的數(shù)據(jù)共享給消費者線程,利用隊列的方式來傳遞數(shù)據(jù),就可以很方便地解決他們之間的數(shù)據(jù)共享問題。但如果生產(chǎn)者和消費者在某個時間段內(nèi),萬一發(fā)生數(shù)據(jù)處理速度不匹配的情況呢?理想情況下,如果生產(chǎn)者產(chǎn)出數(shù)據(jù)的速度大于消費者消費的速度,并且當生產(chǎn)出來的數(shù)據(jù)累積到一定程度的時候,那么生產(chǎn)者必須暫停等待一下(阻塞生產(chǎn)者線程),以便等待消費者線程把累積的數(shù)據(jù)處理完畢,反之亦然。然而,在concurrent包發(fā)布以前,在多線程環(huán)境下,我們每個程序員都必須去自己控制這些細節(jié),尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的復雜度。好在此時,強大的concurrent包橫空出世了,而它也給我們帶來了強大的BlockingQueue。(在多線程領(lǐng)域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒)
ArrayBlockingQueue
ArrayBlockingQueue是一個有邊界的阻塞隊列,它的內(nèi)部實現(xiàn)是一個數(shù)組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。
ArrayBlockingQueue是以先進先出的方式存儲數(shù)據(jù),最新插入的對象是尾部,最新移出的對象是頭部。
下面是一個初始化和使用ArrayBlockingQueue的例子:
ArrayBlockingQueue<String> arrays = new ArrayBlockingQueue<String>(3);
arrays.add("李四");
arrays.add("張軍");
arrays.add("張軍");
// 添加阻塞隊列
arrays.offer("張三", 1, TimeUnit.SECONDS);
LinkedBlockingQueue
LinkedBlockingQueue阻塞隊列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是采用了默認大小為Integer.MAX_VALUE的容量 。它的內(nèi)部實現(xiàn)是一個鏈表。
和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數(shù)據(jù),最新插入的對象是尾部,最新移出的對象是頭部。
下面是一個初始化和使LinkedBlockingQueue的例子:
LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);
linkedBlockingQueue.add("張三");
linkedBlockingQueue.add("李四");
linkedBlockingQueue.add("李四");
System.out.println(linkedBlockingQueue.size());
PriorityBlockingQueue
PriorityBlockingQueue是一個沒有邊界的隊列,它的排序規(guī)則和java.util.PriorityQueue一樣。需要注意,PriorityBlockingQueue中允許插入null對象。所有插入PriorityBlockingQueue的對象必須實現(xiàn) java.lang.Comparable接口,隊列優(yōu)先級的排序規(guī)則就是按照我們對這個接口的實現(xiàn)來定義的。另外,我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器并不保證按照優(yōu)先級順序進行迭代。
SynchronousQueue
SynchronousQueue隊列內(nèi)部僅允許容納一個元素。當一個線程插入一個元素后會被阻塞,除非這個元素被另一個線程消費。
使用BlockingQueue模擬生產(chǎn)者與消費者
class Producer extends Thread{
private BlockingQueue queue;
private volatile boolean flag=true;
private static AtomicInteger count=new AtomicInteger();
public Producer(BlockingQueue queue){
this.queue=queue;
}
@Override
public void run() {
System.out.println(getName()+"生產(chǎn)者線程啟動...");
try {
while (flag){
System.out.println(getName()+"生產(chǎn)者開始生產(chǎn)消息...");
//如果flag為true,queue就入隊列。(原子類進行計數(shù))
Integer i = count.incrementAndGet();
boolean offer = queue.offer(i);
if(offer){
System.out.println(getName()+"生產(chǎn)者生產(chǎn)生產(chǎn)消息:"+i+"成功");
}else {
System.out.println(getName()+"生產(chǎn)者生產(chǎn)生產(chǎn)消息:"+i+"失敗");
}
Thread.sleep(1000);
}
}catch (Exception e){
}finally {
System.out.println(getName()+"生產(chǎn)者線程停止...");
}
}
public void stopThread(){
this.flag=false;
}
}
class Consumer extends Thread{
private BlockingQueue queue;
private volatile boolean flag=true;
public Consumer(BlockingQueue queue){
this.queue=queue;
}
@Override
public void run() {
System.out.println(getName()+"消費者線程啟動...");
try {
while (flag){
System.out.println(getName()+"消費者開始消費消息...");
//如果flag為true,queue就出隊列
Integer poll = (Integer) queue.poll(2, TimeUnit.SECONDS);
if(poll != null){
System.out.println(getName()+"消費者獲取消息:"+poll+"成功");
}else {
System.out.println(getName()+"消費者獲取消息:"+poll+"失敗");
this.flag=false;
}
}
}catch (Exception e){
}finally {
System.out.println(getName()+"消費者線程停止...");
}
}
}
public class ProduceConsumerThread {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);
Producer p1 =new Producer(queue);
Producer p2 =new Producer(queue);
Consumer c1 =new Consumer(queue);
p1.start();
p2.start();
c1.start();
Thread.sleep(3*1000);
p1.stopThread();
p2.stopThread();
}
}

