Java多線程(全)學習筆記(下)

七.CallableFuture接口

    C#能夠把任意方法包裝成線程執行體,包括那些有返回值的方法。Java也從jdk1.5開始,加入了Callable接口用來擴展Runnable接口的功能,Callable接口提供一個call()來加強Runnablerun()。由於call()能夠有返回值,能夠聲明拋出異常。 java

可是Callable是新增的接口 並無繼承Runnable接口,那麼確定不能做爲Runnable target來直接做爲Thread構造方法的參數。必須由一箇中間的類來包裝Callable對象。這個類就是實現了Future接口(繼承至Runnable接口)的FutureTask類。 算法

CODE編程

import java.util.concurrent.Callable;  
  
public class CallableThread implements Callable<Integer> {  
@Override  
public Integer call() throws Exception {  
int i=0;  
for(;i<6;i++){  
System.out.println(Thread.currentThread().getName()+"循環:"+i);  
}  
return i;  
}  
}  
----------------------------------------------------------------------------  
public class TestCallable {  
public static void main(String []args){  
try {  
CallableThread ct=new CallableThread();  
FutureTask<Integer> target=new FutureTask<Integer>(ct);  
for(int i=0;i<5;i++){  
System.out.println(Thread.currentThread().getName()+"循環變量:"+i);  
if(i==2){  
new Thread(target,"子線程").start();  
//boolean isDone():若是Callable任務已完成,則返回true,不然返回false  
System.out.println(target.isDone());  
Thread.sleep(1);  
}  
}  
//V get():返回Callable任務裏call()的返回值,調用該方法會致使阻塞,必須等到子線程結束時纔會獲得返回值  
System.out.println("子返回值是:"+target.get());  
System.out.println(target.isDone());  
} catch (InterruptedException e) {  
e.printStackTrace();  
} catch (ExecutionException e) {  
e.printStackTrace();  
}  
}  
}  
-----------  
結果:  
main循環變量:0  
main循環變量:1  
main循環變量:2  
false  
子線程循環:0  
子線程循環:1  
子線程循環:2  
子線程循環:3  
子線程循環:4  
main循環變量:3  
子線程循環:5  
main循環變量:4  
子返回值是:6  
true



八.線程池

Jdk1.5java也內置支持線程池,爲了提升性能。(當程序中須要建立大量生存期很短暫的線程時,更應該考慮使用線程池) 緩存

Jdk1.5提供一個Executors工廠類來產生線程池。工廠類中包含5個靜態工廠方法來建立線程池: 安全

    一.返回類型是ExecutorService 的共3個: session

1. newFixedThreadPool(int nThreads) 多線程

     建立一個可重用的,具備固定線程數的線程池 併發

CODE ide

public static ExecutorService newFixedThreadPool(int nThreads) {  
    return new ThreadPoolExecutor(nThreads, nThreads,  
                                  0L, TimeUnit.MILLISECONDS,  
                                  new LinkedBlockingQueue<Runnable>());  
}



2. ExecutorService newCachedThreadPool()  工具

      建立一個具備緩存功能的線程池,系統根據須要建立線程,這些線程將會被緩存在線程池中。

CODE

public static ExecutorService newCachedThreadPool() {  
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
                                      60L, TimeUnit.SECONDS,  
                                      new SynchronousQueue<Runnable>());  
}



3.newSingleThreadExecutor()

   建立一個只有單線程的線程池,等於newFixedThreadPool(1)。

CODE

public static ExecutorService newSingleThreadExecutor() {  
        return new FinalizableDelegatedExecutorService  
            (new ThreadPoolExecutor(1, 1,  
                                    0L, TimeUnit.MILLISECONDS,  
                                    new LinkedBlockingQueue<Runnable>()));  
    }



.返回類型是ScheduledExecutorService(是ExecutorService的子類) 的共2個:

    1.newSingleThreadScheduledExecutor()

      建立只有一條線程的線程池,它能夠在指定延遲後執行線程任務

CODE

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {  
        return new DelegatedScheduledExecutorService  
            (new ScheduledThreadPoolExecutor(1));  
    }

 2.newScheduledThreadPool(int corePoolSize)

      建立具備指定線程數的線程池,它能夠在指定延遲後執行線程任務。其中參數指池中所保存的線程數,即便線程時空閒的也被保存在線程池內。

CODE

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



ExecutorService表明儘快執行線程的線程池(只要線程池中有空閒線程當即執行線程任務),程序只須要傳入RunnableCallable對象便可。

   ExecutorService提供三個方法來執行線程

1.Future<?>submitRunnable target):Runnablerun是沒有返回值的因此執行完成後返回null,能夠調用FutureisDone(),isCanclled()來判斷當前target的執行狀態。

2.<T>Future<T>submit(Runnable target,T result):由於Runnablerun是沒有返回值,這裏顯示指定該Runnable對象線程執行完成返回一個值,這個值就是result

3.<T>Future<T>submit(Callable<T> target):Callable中的call是有返回值的

     ScheduledExecutorService提供如下四個方法:

1.ScheduledFuture<V> schedule(Callable(V) c,long delay,TimeUnit unit):指定c任務將在delay延遲後執行。

2.ScheduledFuture<?> sheduleRunnable r,long delay,TimeUnit unit:指定r任務將在delay延遲後執行。

3.ScheduledFuture<?> scheduleAtFixedRate(Runnable r,long initialDelay,long period,TimeUnit unit):指定r任務將在delay延遲後執行,並且以設定的頻率重複執行(在initialDelay後開始執行,而後開始依次在initialDelay+period,initialDelay+period*2...處重複執行)。

4.ScheduledFuture<?>scheduledWithFixedDelay(Runnable r,long nitialDelay,long delay,TimeUnit unit):建立並執行一個在給定初始延遲後首次啓用的按期操做,隨後,在每一次執行停止和下一次執行開始之間都存在給定的延遲。若是任務的任意依次執行時異常,就會取消後序執行。不然,只能經過程序來顯示取消或停止該任務。

當用完一個線程池後,應該調用該線程池的shutdown()方法,啓用了shutdown()方法後線程池再也不接受新任務,但會將之前全部已提交的任務執行完成,而後啓動線程池的關閉序列。

當線程池全部任務都執行完成後,池中全部線程都會死亡,另外也能夠調用線程池的shutdownNow()方法來關閉線程池,該方法試圖中止全部正在執行的活動任務,暫停處理正在等待的任務,並返回等待執行的任務列表。

例子:

class TestThread implements Runnable{  
public void run() {  
           for(int i=0;i<10;i++){  
               System.out.println(Thread.currentThread().getName()+":"+i);  
           }  
}  
}  
public class ThreadPoolTest {  
public static void main(String []args){  
//建立一個固定線程數爲3的線程池  
ExecutorService pool=Executors.newFixedThreadPool(3);  
//像線程池提交10個線程  
for(int i=0;i<10;i++){  
        pool.submit(new TestThread());  
        if(i==4){  
         
         
        }  
}  
//執行完成後關閉線程  
pool.shutdown();  
}  
}  
結果:  
pool-1-thread-2:1  
pool-1-thread-2:2  
pool-1-thread-2:3  
pool-1-thread-1:9  
pool-1-thread-3:8  
pool-1-thread-3:9  
pool-1-thread-2:4  
......



九.附錄:線程相關的類

1.ThreadLocal(線程局部變量)

爲了不併發線程的安全問題,能夠添加支持泛型的ThreadLocal類(ThreadLocal<T>)。經過使用ThreadLocal能夠簡化多線程編程時的併發訪問,使用這個工具類能夠很簡潔的寫出有沒的多線程程序(例如Hibernate的官方提供HibernateUtil類裏設置當前線程的局部變量是當前線程副本中session的值):

Hibernate代碼:

public class HibernateUtil {  
/** 日誌 */  
private static final Log LOG=LogFactory.getLog(HibernateUtil.class);  
/** 線程本地變量*/  
private static final ThreadLocal MAP = new ThreadLocal();  
/** 會話工廠 */  
private static final SessionFactory SESSION_FACTORY;  
private HibernateUtil() {  
}  
static {  
try {  
LOG.debug("HibernateUtil.static - loading config");  
SESSION_FACTORY = new Configuration().configure()  
.buildSessionFactory();  
LOG.debug("HibernateUtil.static - end");  
} catch (HibernateException e) {  
throw new RuntimeException("創建會話工廠錯誤" + e.getMessage(), e);  
}  
}  
/** 
 * 得到一個會話從當前的線程變量,當這個任務完成後,用戶必須返回會話關閉方法 
 */  
public static  Session currentSession()throws HibernateException{  
Session session=(Session)MAP.get();  
//若是會話尚未,就打開會話  
if(session==null){  
session=SESSION_FACTORY.openSession();  
//設置此線程局部變量的值是當前線程副本中session的值  
MAP.set(session);  
}  
return session;  
}  
/** 
 * 關閉會話 
 */  
public static void closeSession(){  
Session session=(Session)MAP.get();  
MAP.set(null);  
if(session!=null){  
session.close();  
}  
}  
}



根據上面代碼來看:

線程局部變量功能很簡單,就是爲每個使用該變量的線程都提供一個變量值的副本,使每個線程均可以獨立地改變本身的副本,而不會和其餘線程的副本衝突。彷彿就好像每個線程均可以徹底擁有該變量

 ThreadLocal類提供的經常使用方法:

         1.T get()返回此線程局部變量中當前線程副本中的值。

         2.void remove()刪除此線程局部變量中當前線程的值。

         3.void setT value):設置此線程局部變量中當前線程副本中的值。

CODE

/** 
 * 線程局部變量測試 
 * @author Cloudy 
 * 
 */  
class Accout{  
//定義一個ThreadLocal變量,只要調用該類的線程都會保留該變量的一個副本  
private ThreadLocal<String>  threadLocal=new ThreadLocal<String>();  
//初始化threadLocal  
public Accout(String str) {  
this.threadLocal.set(str);  
System.out.println("-------初始化(爲ThreadLocal在main中副本)值是:"+this.threadLocal.get());  
}  
public String getThreadLocal() {  
return threadLocal.get();  
}  
public void setThreadLocal(String threadLocal) {  
this.threadLocal.set(threadLocal);  
}  
}  
/**定義一個線程*/  
class MyThread implements Runnable{  
//模擬一個Accout  
private Accout accout;  
public MyThread(Accout accout) {  
super();  
this.accout = accout;  
}  
//線程執行體  
public void run() {  
for(int i=0;i<3;i++){  
if(i==2){  
//設置此線程局部變量的值爲當前線程名字  
accout.setThreadLocal(Thread.currentThread().getName());  
}  
System.out.println(i+"------"+Thread.currentThread().getName()+"線程局部變量副本值:"+accout.getThreadLocal());  
}  
}  
}  
public class ThreadLocalVarTest {  
public static void main(String []args){  
ExecutorService pool=Executors.newFixedThreadPool(3);  
//啓動三條線程,公用同一個Accout  
Accout ac=new Accout("ThreadLocal本尊");  
/* 
 * 雖然Accout類的只有一個變量因此ThreadLocal類型的變量就致使了同一個Accout對象, 
 * 當i=2後,將會看到3條線程訪問同一個ac 而看到不一樣的ThreadLocal值。 
 */  
pool.submit(new MyThread(ac));  
pool.submit(new MyThread(ac));  
pool.submit(new MyThread(ac));  
pool.shutdown();  
}  
}



結果:

-------初始化(爲ThreadLocal在main中副本)值是:ThreadLocal本尊

0------pool-1-thread-1線程局部變量副本值:null

1------pool-1-thread-1線程局部變量副本值:null

2------pool-1-thread-1線程局部變量副本值:pool-1-thread-1

0------pool-1-thread-2線程局部變量副本值:null

1------pool-1-thread-2線程局部變量副本值:null

2------pool-1-thread-2線程局部變量副本值:pool-1-thread-2

0------pool-1-thread-3線程局部變量副本值:null

1------pool-1-thread-3線程局部變量副本值:null

2------pool-1-thread-3線程局部變量副本值:pool-1-thread-3

總結:

     ThreadLocal並不能代替同步機制,二者面向的問題領域不一樣,同步機制是爲了多個線程同步對相同資源的併發訪問,是多個線程之間進行通訊的有效方式。而ThreadLocal是隔離多個線程的數據共享,根本就沒有在多個線程之間共享資源,也就更不用對多個線程同步了。

因此:若是進行多個線程之間共享資源,達到線程之間通訊功能,就同步。

      若是僅僅須要隔離多個線程之間的共享衝突,就是用ThreadLocal

1.包裝線程不安全的集合成爲線程安全集合

Java集合中的ArrayListLinkedListHashSetTreeSetHashMap都是線程不安全的(線程不安全就是當多個線程想這些集合中放入一個元素時,可能會破壞這些集合數據的完整性)

如何將上面的集合類包裝成線程安全的呢?

例子:使用CollectionssynchronizedMap方法將一個普通HashMap包裝成線程安全的類

HashMap hm=Collections.synchronizedMap(new Map());

若是須要包裝某個集合成線程安全集合,則應該在建立以後當即包裝如上。

3.線程安全的集合類

     位於java.util.concurrent包下的ConcurrentHashMap集合和ConcurrentLinkedQueue集合都支持併發訪問,分別對應支持併發訪問的HashMapQueue,它們均可以支持多線程併發寫訪,這些寫入線程的全部操做都是線程安全的,但讀取的操做沒必要鎖定。(爲何?由於算法  我也看不懂)

     當多個線程共享訪問一個公共集合時,使用ConcurrentLinkedQueue是一個恰當的選擇,由於ConcurrentLinkedQueue不容許使用null元素。ConcurrentLinkedQueue實現了多線程的高效訪問,多線程訪問ConcurrentLinkedQueue集合時不須要等待。

      ConcurrentHashMap支持16條多線程併發寫入。

      用迭代器訪問這兩種可支持多線程的集合而言,該迭代器可能不反應出建立迭代器以後所作的修改,但不會拋出異常,而若是Collection做爲對象,迭代器建立以後修改,則會拋出ConcurrentModificationException

相關文章
相關標籤/搜索