JAVA\Android 多線程實現方式及並發與同步


轉載: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開頭的類都是原子類。例如AtomicIntegerAtomicReference

什么是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,對其的操作必須是放和取交替完成的。

LinkedBlockingQueueArrayBlockingQueue比較起來,它們背后所用的數據結構不一樣,導致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異常。
並發容器支持並發的遍歷和並發的更新。主要的類有ConcurrentHashMapCopyOnWriteArrayList 和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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM