轉載:https://blog.csdn.net/csdn_aiyang/article/details/65442540
概述
進程是系統的執行單位, 一般一個應用程序 即是一個進程,程序啟動時系統默認有一個主線程,即是UI線程,我們知道不能做耗時任務,否則ANR程序無響應。
這時需要借助子線程實現,即多線程。 由於線程是系統CPU的最小單位,用多線程其實就是為了更好的利用cpu的資源。
問1.線程狀態
1、wait()。 使一個線程處於等待狀態,並且釋放所有持有對象的lock鎖,直到notify()/notifyAll()被喚醒后放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運行狀態(Runnable)。
2、sleep()。 使一個線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉Interrupted異常,醒來后進入runnable狀態,等待JVM調度。
3、notify()。使一個等待狀態的線程喚醒, 注意並不能確切 喚醒等待狀態線程,是由JVM決定且不按優先級。
4、notifyAllAll()。使所有等待狀態的線程喚醒,注意並不是給所有線程上鎖,而是讓它們競爭。
5、join()。 使一個線程中斷,IO完成會回到Runnable狀態,等待JVM的調度。
6、Synchronized()。 使Running狀態的線程加同步鎖使其進入(lock blocked pool ),同步鎖被釋放 進入可運行狀態(Runnable)。
注意:當線程在runnable狀態時是處於被調度的線程,此時的調度順序是不一定的。
Thread類中的yield方法可以讓一個running狀態的線程轉入runnable。
基礎概念
1、 並行。多個cpu實例或多台機器同時執行一段代碼,是真正的同時。
2、並發。通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。
3、線程安全。指在並發的情況之下,該代碼經過多線程使用,線程的調度順序不影響任何結果。
線程不安全就意味着線程的調度順序會影響最終結果,比如某段代碼不加事務去並發訪問。
4、線程同步。 指的是通過人為的控制和調度,保證共享資源的多線程訪問成為 線程安全,來保證結果的准確。
如某段代碼加入@synchronized關鍵字。線程安全的優先級高於性能優化。
5、原子性。一個操作或者一系列操作,要么全部執行要么全部不執行。 數據庫中的“事物”就是個典型的院子操作。
6、可見性。當一個線程修改了共享屬性的值,其它線程能立刻看到共享屬性值的更改。
比如 JMM分為 主存和工作內存, 共享屬性的修改過程是在主存中讀取並復制到工作內存中,在工作內存中修改完成之后,再刷新主存中的值。
若線程A在工作內存中修改完成但還來得及刷新主存中的值,這時線程B訪問該屬性的值仍是舊值。這樣可見性就沒法保證。
7、有序性。 程序運行時代碼邏輯的順序,在實際執行中不一定有序,為了提高性能,編譯器和處理器 都會對代碼進行重新排序。
前提是,重新排序的結果要 和單線程執行程序順序一致。
問1. 常見多線程方式
1、繼承Thread類,重寫run函數方法:
class xx extends Thread{
public void run(){
Thread.sleep(1000); //線程休眠1000毫秒,sleep使線程進入Block狀態,並釋放資源
}
}
xx.start(); //啟動線程,run函數運行
2、實現Runnable接口,重寫run函數方法:
Runnable run =new Runnable() {
@Override
public void run() {
}
}
3、實現Callable接口,重寫call函數方法:
Callable call =new Callable() {
@Override
public Object call() throws Exception {
return null;
}
}
小結:Callable 與 Runnable 對比。
相同:都是可被其它線程執行的任務。
不同:
①Callable規定的方法是call(), 而Runnable規定的方法是run().
②Callable的任務執行后可返回值,而Runnable的任務是不能返回值的
③call()方法可拋出異常,而run()方法是不能拋出異常的。
④運行Callable任務可拿到一個 Future對象,Future表示異步計算的結果。 通過Future對象可了解任務執行情況, 可取消任務的執行。
問2、HandlerThread:
handlerThread = new HandlerThread("MyNewThread");//自定義線程名稱
handlerThread.start();
mOtherHandler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg){
if (msg.what == 0x124){
try {
Log.d("HandlerThread", Thread.currentThread().getName());
Thread.sleep(5000);//模擬耗時任務
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
HandlerThread的好處是代碼看起來沒前面的版本那么亂,相對簡潔一點。
還有一個好處就是 通過handlerThread.quit() 或者 quitSafely(),使線程結束自己的生命周期。
問3、IntentService:
最后是IntentService,相信很多人也不陌生,它是Service的子類,用法跟Service也差不多,就是實現的方法名字不一樣,
耗時邏輯應放在onHandleIntent(Intent intent)的方法體里,它同樣有着退出啟動它的Activity后不會被系統殺死的特點,
而且當任務執行完后會自動停止,無須手動去終止它。
例如在APP里我們要實現一個下載功能,當退出頁面后下載不會被中斷,那么這時候IntentService就是一個不錯的選擇了。
問4.線程各種同步的含義:
Synchronized 同步:
由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時, 內置鎖會保護整個方法。 在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
補充: synchronized關鍵字也可以修飾 靜態方法,此時如果調用該靜態方法,將會鎖住整個類。
1、方法同步。
給方法增加synchronized修飾符就可以成為同步方法,可以是靜態方法、非靜態方法,但不能是抽象方法、接口方法。
public synchronized void aMethod() {
// do something
}
public static synchronized void anotherMethod() {
// do something
}
使用詳解:
線程在執行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執行完它所調用的同步方法 並從中退出,從而導致它釋放了該對象的同步鎖之后。
在一個對象被某個線程鎖定之后,其他線程是可以訪問這個對象的所有非同步方法的。
2、塊同步。
同步塊是通過鎖定一個指定的對象,來對塊中的代碼進行同步;
同步方法和 同步塊之間的相互制約只限於同一個對象之間, 靜態同步方法只受它所屬類的其它靜態同步方法的制約,而跟這個類的實例沒有關系。
如果一個對象既有同步方法,又有同步塊,那么當其中任意一個同步方法或者同步塊被某個線程執行時,這個對象就被鎖定了,其他線程無法在此時訪問這個對象的同步方法,也不能執行同步塊。
使用方法同步保護共享數據。示例:
public class ThreadTest implements Runnable{
public synchronized void run(){
for(int i=0;i<10;i++) {
System.out.print(" " + i);
}
}
public static void main(String[] args) {
Runnable r1 = new ThreadTest();
Runnable r2 = new ThreadTest();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}}
示例詳解:
代碼中可見,run()被加上了synchronized 關鍵字,但保護的並不是共享數據。
因為程序中兩個線程對象 t1、t2 其實是另外兩個線程對象 r1、r2 的線程,這個聽起來繞,但是一眼你就能看明白;因為不同的線程對象的數據是不同的, 即 r1,r2 有各自的run()方法,所以輸出結果就無法預知。
這時使用 synchronized 關鍵字可以讓某個時刻,只有一個線程可以訪問該對象synchronized數據。每個對象都有一個“鎖標志”,當這個對象的一個線程訪問這個對象的某個synchronized 數據時,這個對象的所有被synchronized 修飾的數據將被上鎖(因為“鎖標志”被當前線程拿走了),只有當前線程訪問完它要訪問的synchronized 數據時,當前線程才會釋放“鎖標志”,這樣同一個對象的其它線程才有機會訪問synchronized 數據。
接下來,我們把 r2 給注釋掉, 即只保留一個 r 對象。如下:
public class ThreadTest implements Runnable{
public synchronized void run(){
for(int i=0;i<10;i++){
System.out.print(" " + i);
}
}
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}}
示例詳解:
如果你運行1000 次這個程序,它的輸出結果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因為這里的synchronized 保護的是共享數據。
t1,t2 是同一個對象(r)的兩個線程,當其中的一個線程(例如:t1)開始執行run()方法時,由於run()受synchronized保護,所以同一個對象的其他線程(t2)無法訪問synchronized 方法(run 方法)。只有當t1執行完后t2 才有機會執行。
4、使用塊同步,示例:
public class ThreadTest implements Runnable{
public void run(){
synchronized(this){ //與上面示例不同於關鍵字使用
for(int i=0;i<10;i++){
System.out.print(" " + i);
}
}
}
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
示例詳解:
這個與上面示例的運行結果也一樣的。這里是把保護范圍縮到最小,this 代表 ‘這個對象’ 。沒有必要把整個run()保護起來,run()中的代碼只有一個for循環,所以只要保護for 循環就可以了。
最后,再看一個示例:
public class ThreadTest implements Runnable{
public void run(){
for(int k=0;k<5;k++){
System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
}
synchronized(this){
for(int k=0;k<5;k++) {
System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
}} }
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r,"t1_name");
Thread t2 = new Thread(r,"t2_name");
t1.start();
t2.start();
} }
//運行結果:
t1_name : for loop : 0
t1_name : for loop : 1
t1_name : for loop : 2
t2_name : for loop : 0
t1_name : for loop : 3
t2_name : for loop : 1
t1_name : for loop : 4
t2_name : for loop : 2
t1_name : synchronized for loop : 0
t2_name : for loop : 3
t1_name : synchronized for loop : 1
t2_name : for loop : 4
t1_name : synchronized for loop : 2
t1_name : synchronized for loop : 3
t1_name : synchronized for loop : 4
t2_name : synchronized for loop : 0
t2_name : synchronized for loop : 1
t2_name : synchronized for loop : 2
t2_name : synchronized for loop : 3
t2_name : synchronized for loop : 4
示例詳解:
第一個for 循環沒有受synchronized 保護。對於第一個for 循環,t1,t2 可以同時訪問。運行結果表明t1 執行到了k=2 時,t2 開始執行了。t1 首先執行完了第一個for 循環,此時t2還沒有執行完第一個for 循環(t2 剛執行到k=2)。t1 開始執行第二個for 循環,當t1的第二個for 循環執行到k=1 時,t2 的第一個for 循環執行完了。t2 想開始執行第二個for 循環,但由於t1 首先執行了第二個for 循環,這個對象的鎖標志自然在t1 手中(synchronized 方法的執行權也就落到了t1 手中),在t1 沒執行完第二個for 循環的時候,它是不會釋放鎖標志的。所以t2 必須等到t1 執行完第二個for 循環后,它才可以執行第二個for 循環。
Volatile 同步:
a. volatile關鍵字為域變量的訪問,提供了一種免鎖機制。
b.使用volatile修飾域相當於告訴虛擬機,該域可能會被其他線程更新
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值。
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量 。
例如:
在上面的例子當中,只需在account前面加上volatile修飾,即可實現線程同步。
代碼實例:
//只給出要修改的代碼,其余代碼與上同
class Bank {
//需要同步的變量加上volatile
private volatile int account = 100;
public int getAccount() {
return account;
}
//這里不再需要synchronized
public void save(int money) {
account += money;
}
}
注:多線程中的非同步問題,主要出現在對域的讀寫上,如果讓域自身避免這個問題,則就不需要修改操作該域的方法。
用final域,有鎖保護的域 和volatile域 可以避免非同步的問題。
原子變量同步:
需要使用線程同步的根本原因在於對普通變量的操作不是原子的。那么什么是原子操作呢?
原子操作就是指將 讀取變量值、修改變量值、保存變量值看成一個整體來操作即-這幾種行為要么同時完成,要么都不完成。
在java的util.concurrent.atomic包中提供了創建了原子類型變量的工具類,使用該類可以簡化線程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在應用程序中(如以原子方式增加的計數器),但不能用於替換Integer;
可擴展Number,允許那些 處理--機遇數字類的工具和實用工具 進行統一訪問。
AtomicInteger類常用方法:
AtomicInteger(int initialValue) : 創建具有給定初始值的新的
AtomicInteger.addAddGet(int dalta) : 以原子方式 將給定值與當前值相加
get() : 獲取當前值
class Bank {
private AtomicInteger account = new AtomicInteger(100);
public AtomicInteger getAccount() {
return account;
}
public void save(int money) {
account.addAndGet(money);
}
}
補充--原子操作主要有:
對於引用變量和大多數原始變量(long和double除外)的讀寫操作;
對於所有使用volatile修飾的變量(包括long和double)的讀寫操作。另外,可以使用線程池進行管理及優化。
我的相關文章推薦鏈接地址點擊跳轉:線程優化及線程池管理。
阻塞隊列同步:
前面同步方式都是在底層實現的線程同步,但是我們在實際開發當中,應當盡量遠離底層結構。
使用javaSE5.0版本中新增的java.util.concurrent包,將有助於簡化開發。本小節主要是使用LinkedBlockingQueue<E>來實現線程的同步。
LinkedBlockingQueue<E>是一個基於已連接節點的,范圍任意的blocking queue。 隊列是先進先出的順序(FIFO)。
LinkedBlockingQueue 類常用方法 LinkedBlockingQueue() : 創建一個容量為Integer.MAX_VALUE的LinkedBlockingQueue 。
put(E e) : 在隊尾添加一個元素,如果隊列滿則阻塞;
size() : 返回隊列中的元素個數 ; take() : 移除並返回隊頭元素,如果隊列空則阻塞;
代碼實例: 實現商家生產商品和買賣商品的同步。
注:BlockingQueue<E>定義了阻塞隊列的常用方法,尤其是三種添加元素的方法,我們要多加注意,當隊列滿時:
add()方法會拋出異常
offer()方法返回false
put()方法會阻塞。
重入鎖同步:
在 JavaSE5.0中 新增了一個 java.util.concurrent 包來支持同步。
ReentrantLock類是 可重入、互斥、實現了Lock接口的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,並且擴展了其能力。
ReenreantLock類的常用方法有:
ReentrantLock() : 創建一個ReentrantLock實例
lock() : 獲得鎖 ; unlock() : 釋放鎖 ;
注:ReentrantLock()還有一個可以創建公平鎖的構造方法,但由於能大幅度降低程序運行效率,不推薦使用 。例如:
class Bank {
private int account = 100;
private Lock lock = new ReentrantLock(); //需要聲明這個鎖
public int getAccount() {
return account;
}
public void save(int money) { //這里不再需要synchronized
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
}
注:關於Lock對象和synchronized關鍵字的選擇:
a.最好兩個都不用,使用一種java.util.concurrent包提供的機制,能夠幫助用戶處理所有與鎖相關的代碼。
b.如果synchronized關鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼
c.如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現死鎖,通常在finally代碼釋放鎖
局部變量同步:
如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,
而不會對其他線程產生影響。
ThreadLocal 類的常用方法:
ThreadLocal() : 創建一個線程本地變量
get() : 返回此線程局部變量的當前線程副本中的值
initialValue() : 返回此線程局部變量的當前線程的"初始值"
set(T value) : 將此線程局部變量的當前線程副本中的值設置為value
public class Bank{
//使用ThreadLocal類管理共享變量account
private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
public void save(int money){
account.set(account.get()+money);
}
public int getAccount(){
return account.get();
}
}
注:ThreadLocal與同步機制;
a.ThreadLocal與同步機制都是為了解決多線程中相同變量的訪問沖突問題。
b.前者采用以"空間換時間"的方法,后者采用以"時間換空間"的方式
4、AsyncTask:
具體的使用代碼就不貼上來了,可以去看我的一篇博文。但值得一說的是,上面說過HandlerThread只開一條線程,任務都被阻塞在一個隊列中,那么就會使阻塞的任務延遲了,而AsyncTask開啟線程的方法asyncTask.execute()默認是也是開啟一個線程和一個隊列的,不過也可以通過asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)開啟一個含有5個新線程的線程池,也就是說有個5個隊列了,假如說你執行第6個耗時任務時,除非前面5個都還沒執行完,否則任務是不會阻塞的,這樣就可以大大減少耗時任務延遲的可能性,這也是它的優點所在。當你想多個耗時任務並發的執行,那你更應該選擇AsyncTask。
---------------------
什么是原子操作? Java Concurrency API中有哪些原子操作類?
原子操作是執行單個任務單元的操作,這個操作不需要干擾其他操作,可以理解為當前情況下不可再分的操作,遠在操作是多線程環境下避免數據不一致而存在的必需品。
int++就不是原子操作,如果一個線程讀取它的值並行+1操作,而另外一個線程讀取了舊的值,則會導致錯誤的結果。為了解決這個問題,
我們需要確保遞增操作是原子的,可以使用同步原語(synchronization),也可以使用Java5 包裝的AtomicInteger
直接完成原子操作。
在包java.util.concurrent.atomic
下面的一Atomic開頭的類都是原子類。例如AtomicInteger
,AtomicReference
等
什么是Java Cuncurrency API
中Lock 接口? 相對於同步(synchronization
)有什么優勢?
首先我們看下Lock接口的定義:
Lock是一個控制多線程訪問共享資源的工具類,比使用synchronized方法或者語句有了更加擴展性的操作,結構靈活,可以有完全不同的屬性,
也可以支持多個相關類的條件對象。使用方法如下:
{@code
Lock l = ...;
l.lock();
try {
// 訪問鎖保護的共享資源
} finally {
l.unlock();
}}
- 相對於synchronization 有如下優點:
- 可以使鎖更公平
- 可以使線程在等待鎖的時候響應中斷;
- 可以讓線程嘗試獲取鎖,並在無法獲取鎖的時候立即返回 或者等待一段時間
- 可以在不同的作用域,以不同的順序獲取和釋放鎖
什么是阻塞隊列(BlockingQueue)?如何使用阻塞隊列實現生產者—消費者問題?
BlockingQueue是一個阻塞隊列,顧名思義,當進行檢索和刪除的時候,如果隊列為空,則阻塞等待直至隊列非空,當添加元素到隊列的時候,如果沒有空間會阻塞等待到隊列有空間為止。接口定義如下圖所示:

BlockingQueue不接受Null值,如果存儲Null會拋出空指針異常。BlocingQueue的實現都是線程安全的,使用內部鎖或者其他的並發控制方法。
BlockingQueue定義的常用方法如下:
- add(anObject):把anObject加到BlockingQueue里,如果BlockingQueue可以容納,則返回true,否則拋出異常。
- offer(anObject):表示如果可能的話,將anObject加到BlockingQueue里,即如果BlockingQueue可以容納,則返回true,否則返回false。
- put(anObject):把anObject加到BlockingQueue里,如果BlockingQueue沒有空間,則調用此方法的線程被阻斷直到BlockingQueue里有空間再繼續。
- poll(time):取走BlockingQueue里排在首位的對象,若不能立即取出,則可以等time參數規定的時間,取不到時返回null。
- take():取走BlockingQueue里排在首位的對象,若BlockingQueue為空,阻斷進入等待狀態直到BlockingQueue有新的對象被加入為止。
BlockingQueue有四個具體的實現類,根據不同需求,選擇不同的實現類:
- ArrayBlockingQueue:規定大小的BlockingQueue,其構造函數必須帶一個int參數來指明其大小。其所含的對象是以FIFO(先入先出)順序排序的。
- LinkedBlockingQueue:大小不定的BlockingQueue,若其構造函數帶一個規定大小的參數,生成的BlockingQueue有大小限制,若不帶大小參數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定。其所含的對象是以FIFO順序排序的。
- PriorityBlockingQueue:類似於LinkedBlockingQueue,但其所含對象的排序不是FIFO,而是依據對象的自然排序順序或者是構造函數所帶的Comparator決定的順序。
- SynchronousQueue:特殊的BlockingQueue,對其的操作必須是放和取交替完成的。
LinkedBlockingQueue
和ArrayBlockingQueue
比較起來,它們背后所用的數據結構不一樣,導致LinkedBlockingQueue的數據吞吐量要大於ArrayBlockingQueue,但在線程數量很大時其性能的可預見性低於ArrayBlockingQueue。
實現生產者—消費者代碼如下:Message.java
public class Message {
private String msg ;
public String getMsg() {
return msg;
}
public void setMsg(final String msg) {
this.msg = msg;
}
public Message(final String msg) {
this.msg = msg;
}
}
Producer.java
import java.util.concurrent.BlockingQueue;
public class Producer implements Runnable {
private BlockingQueue<Message> messageBlockingDeque;
public Producer(final BlockingQueue<Message> messageBlockingDeque) {
this.messageBlockingDeque = messageBlockingDeque;
}
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
Message msg = new Message(" "+i);
try {
Thread.sleep(i);
messageBlockingDeque.put(msg);
System.out.println("Produced:"+msg.getMsg());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message msg = new Message("exit");
try {
messageBlockingDeque.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Consumer.java
public class Consumer implements Runnable {
private BlockingQueue<Message> blockingDeque;
public Consumer(final BlockingQueue<Message> blockingDeque) {
this.blockingDeque = blockingDeque;
}
@Override
public void run() {
Message message;
try {
while((message= blockingDeque.take()).getMsg()!="exit"){
Thread.sleep(10);
System.out.println("Consumed:"+message.getMsg());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ProductConsumerService.java
public class ProductConsumerService {
public static void main(String[] args) {
BlockingQueue<Message> queue = new ArrayBlockingQueue<Message>(10);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer((queue));
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(producer);
executorService.submit(consumer);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
}
}
什么是Executors類?
Executors為Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable類提供了一些工具方法。
Executors可以用於方便的創建線程池。
什么是Executors框架?
在Java5中, Executor框架隨同java.util.concurrent.Executor
接口一同被引入,Executor框架標准化了線程的調用,調度,執行以及異步任務的控制。
無節制的線程創建會引起內存溢出,創建有限線程數的線程池(ThreadPool)是一個好的選擇,線程可以預先創建,回收再利用。
Executor框架促進了Java中線程池的創建,示例代碼如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
什么是並發集合類?
Java集合類都是快速失效的,這就意味着當集合被改變且一個線程在使用迭代器遍歷集合的時候,迭代器的next()方法
將拋出ConcurrentModificationException
異常。
並發容器支持並發的遍歷和並發的更新。主要的類有ConcurrentHashMap
, CopyOnWriteArrayList
和CopyOnWriteArraySet
Java 8中,Concurrency API有哪些改進?
一些改進如下:
ConcurrentHashMap
改進了compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() 和 search() 方法.
CompletableFuture that may be explicitly completed (setting its value and status).
Executors
的newWorkStealingPool() 方法創建一個 work-stealing 線程池,使用目前機器上可用的處理器作為它的並行級別。
什么是FutureTask 類?
FutureTask是一個可取消的異步計算類,它實現了Future接口,因為可以在Executors中使用,執行異步處理操作,大多數情況下,我們不需要FutureTask類。
僅僅當我們打算重寫Future接口的一些方法並保持原來基礎的實現是,它就變得非常有用。我們可以僅僅繼承於它並重寫我們需要的方法。
什么是Callable和Future?
Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,但它可以返回任何一個對象或者拋出一個異常。
Callable接口使用泛型去定義它的返回類型。Executors類提供了一些有用的方法去在線程池中執行Callable內的任務。由於Callable任務是並行的,我們必須等待它返回的結果。java.util.concurrent.Future應運而生。在線程池提交Callable任務后返回了一個Future對象,使用它我們可以知道Callable任務的狀態和得到Callable返回的執行結果。Future提供了get()方法讓我們可以等待Callable結束並獲取它的執行結果,示例代碼如下:
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return Thread.currentThread().getName();
}
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
List<Future<String>> list = new ArrayList<Future<String>>();
Callable<String> callable = new MyCallable();
for (int i = 0; i < 10; i++) {
Future<String> future = service.submit(callable);
list.add(future);
}
for (Future<String> fut : list) {
try {
System.out.println(new Date() + "::" + fut.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
service.shutdown();
}
}
運行結果:
Mon Oct 06 17:24:52 CST 2014::pool-1-thread-1
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-2
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-3
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-4
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-5
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-6
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-7
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-8
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-9
Mon Oct 06 17:24:53 CST 2014::pool-1-thread-10