C#能夠把任意方法包裝成線程執行體,包括那些有返回值的方法。Java也從jdk1.5開始,加入了Callable接口用來擴展Runnable接口的功能,Callable接口提供一個call()來加強Runnable的run()。由於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.5後java也內置支持線程池,爲了提升性能。(當程序中須要建立大量生存期很短暫的線程時,更應該考慮使用線程池) 緩存
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表明儘快執行線程的線程池(只要線程池中有空閒線程當即執行線程任務),程序只須要傳入Runnable或Callable對象便可。
ExecutorService提供三個方法來執行線程:
1.Future<?>submit(Runnable target):Runnable中run是沒有返回值的因此執行完成後返回null,能夠調用Future的isDone(),isCanclled()來判斷當前target的執行狀態。
2.<T>Future<T>submit(Runnable target,T result):由於Runnable中run是沒有返回值,這裏顯示指定該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<?> shedule(Runnable 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 ......
爲了不併發線程的安全問題,能夠添加支持泛型的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 set(T 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。
Java集合中的ArrayList,LinkedList,HashSet,TreeSet,HashMap都是線程不安全的(線程不安全就是當多個線程想這些集合中放入一個元素時,可能會破壞這些集合數據的完整性)
如何將上面的集合類包裝成線程安全的呢?
例子:使用Collections的synchronizedMap方法將一個普通HashMap包裝成線程安全的類
HashMap hm=Collections.synchronizedMap(new Map());
若是須要包裝某個集合成線程安全集合,則應該在建立以後當即包裝如上。
位於java.util.concurrent包下的ConcurrentHashMap集合和ConcurrentLinkedQueue集合都支持併發訪問,分別對應支持併發訪問的HashMap和Queue,它們均可以支持多線程併發寫訪,這些寫入線程的全部操做都是線程安全的,但讀取的操做沒必要鎖定。(爲何?由於算法 我也看不懂)
當多個線程共享訪問一個公共集合時,使用ConcurrentLinkedQueue是一個恰當的選擇,由於ConcurrentLinkedQueue不容許使用null元素。ConcurrentLinkedQueue實現了多線程的高效訪問,多線程訪問ConcurrentLinkedQueue集合時不須要等待。
ConcurrentHashMap支持16條多線程併發寫入。
用迭代器訪問這兩種可支持多線程的集合而言,該迭代器可能不反應出建立迭代器以後所作的修改,但不會拋出異常,而若是Collection做爲對象,迭代器建立以後修改,則會拋出ConcurrentModificationException。