基于JDK5.0的一些Thread總結(jié) |
---|
wangjiong 發(fā)表于 2006 年 06 月 15 日 |
在新的JDK5.0中,對thread做了一些改進,我通過閱讀一些資料寫下了下面的總結(jié),請大家看看。 1.創(chuàng)建線程 在java中實現(xiàn)多線程有兩種方法,一個是直接繼承Thread類,一個是實現(xiàn)Runnable接口,但是推薦的是第二種。因為在邏輯上應該要把一個線程要做的事情以及做這個事情的方法分開;對于Thread來講,它只負責線程的操作,而具體要做的事情就應該放在Runnable中。但不管是那種方式,都要實現(xiàn)public void run()方法,但啟動線程用start而不是run。 2.終止線程 在1.0中,可以用stop方法來終止,但是現(xiàn)在這種方法已經(jīng)被禁用了,改用interrupt方法。interrupt方法并不是強制終止線程,它只能設(shè)置線程的interrupted狀態(tài),而在線程中一般使用一下方式: while (!Thread.currentThread().isInterrupted() && more work to do) { do more work } 而被block的線程在被調(diào)用interrupt時會產(chǎn)生InterruptException,此時是否終止線程由本線程自己決定。程序的一般形式是: public void run() { try { . . . while (!Thread.currentThread().isInterrupted() && more work to do) { do more work } } catch(InterruptedException e) { // thread was interrupted during sleep or wait } finally { cleanup, if required } // exiting the run method terminates the thread } Thread.sleep方法也會產(chǎn)生InterruptedException,因此,如果每次在做完一些工作后調(diào)用了sleep方法,那么就不用檢查isInterrupted,而是直接捕捉InterruptedException。 在捕捉到InterruptedException時,如果沒有其他的事情可做,最好做一下處理,不能用{} 1) void mySubTask() { . . . try { sleep(delay); } catch (InterruptedException e) { Thread().currentThread().interrupt(); } . . . } 或是 2) void mySubTask() throws InterruptedException { . . . sleep(delay); . . . } 3.線程狀態(tài) New:當使用new創(chuàng)建一個線程時 Runnable: 調(diào)用start或是從blocked狀態(tài)出來時 Blocked:sleep, block on input/output, try to acquire lock, suspend, wait. Dead: 運行完成或有exception產(chǎn)生。 4.線程優(yōu)先級 可以設(shè)置線程優(yōu)先級,但是不能保證高優(yōu)先級的線程就會被先運行 5.線程組 可以把多個線程加到一個線程組里面去,這樣可以對這些線程進行一些統(tǒng)一的操作,例如 ThreadGroup g = new ThreadGroup(groupName) ... g.interrupt(); // interrupt all threads in group g 6.為Uncaught Exceptions設(shè)置Handlers 在java 5.0中,可以為線程中產(chǎn)生的unchecked exception設(shè)置一個處理器,這個處理器必須實現(xiàn)UncaughtExceptionHandler接口。 可以調(diào)用線程實例的setUncaughtExceptionHandler方法為每個線程設(shè)置一個處理器,也可以調(diào)用Thread.setDefaultUncaughtExceptionHandler來為所有的線程設(shè)置一個默認的處理器。如果沒有給每一個線程設(shè)置處理器,那線程會首先使用線程組的處理器,如果還沒有再使用默認的處理器。 7.Synchronization 多線程很重要的一個問題就是同步的問題,如果不解決好同步的問題一個是可能會引起數(shù)據(jù)的混亂,而且還有可能造成線程的死鎖。在Java 5.0之前,用synchronized來解決這個問題,在5.0中加入了一個新的類:ReentrantLock 使用lock的基本形式是: myLock.lock(); // a ReentrantLock object try { critical section } finally { myLock.unlock(); // make sure the lock is unlocked even if an exception is thrown } 這個鎖被稱為Reentrant的原因是在一個線程中可以重復多次申請同一個鎖,系統(tǒng)會保留加鎖的次數(shù),而在解鎖的時候也就必須執(zhí)行相同次數(shù)。 在一個線程已經(jīng)得到鎖可以執(zhí)行程序的時候,可能會發(fā)現(xiàn)需要的條件還不能滿足,這時他就必須等待直到條件滿足。但是因為它已經(jīng)對所需要操作的東西加了鎖,其他的線程不能訪問,因此它又可能會永遠等待下去?,F(xiàn)在可以用Condition Object來避免這種情況。 sufficientFunds = bankLock.newCondition(); 如果條件不滿足: sufficientFunds.await(); 這時線程就會釋放鎖并進入blocked狀態(tài),其他線程就有機會執(zhí)行操作。當其他線程執(zhí)行完后,就可通知等待的線程繼續(xù)執(zhí)行它的操作了: sufficientFunds.signalAll(); 當然也可以調(diào)用singal方法,這樣效率會高一些,但是有一定的危險性,因為它的喚醒具有隨機性。 在5.0之前,采用的是synchronized關(guān)鍵字來進行同步,但是和lock相比它有一些局限性: 1. 申請鎖的線程不能被interrupt 2. 沒有timeout設(shè)置 3. 只有一個隱性的condition條件 另外,在申請鎖的時候可以用tryLock方法,它會返回一個bool值來表示鎖是否申請成功,如果沒有成功,程序就可以做其他的事情了。 tryLock, await方法都可以被interrupt。 java.util.concurrent.locks包中提供了兩種鎖,一個就是ReentrantLock,另一個是ReentrantReadWriteLock,一般用于多操作遠遠多于寫操作的時候: private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock(); 8. Callables and Futures 實現(xiàn)多線程時一般用的是Runnable接口,但是他有一個問題就是他沒有參數(shù)和返回值,所以當執(zhí)行一個線程需要返回一個值的時候就不是很方便了。Callable接口和Runnable差不多,但是他提供了參數(shù)和返回值: public interface Callable<V> { V call() throws Exception; } 而Future接口可以保留異步執(zhí)行的值: public interface Future<V> { V get() throws . . .; V get(long timeout, TimeUnit unit) throws . . .; void cancel(boolean mayInterrupt); boolean isCancelled(); boolean isDone(); } FutureTask可以很方便的把Callable轉(zhuǎn)換成Future和Runnable: Callable<Integer> myComputation = . . .; FutureTask<Integer> task = new FutureTask<Integer>(myComputation); Thread t = new Thread(task); // it‘s a Runnable t.start(); . . . Integer result = task.get(); // it‘s a Future 9.用Executors創(chuàng)建線程池 用線程池有兩個好處:1. 減少創(chuàng)建線程的開銷。2. 控制線程的數(shù)量。 EXecutors提供了一些方法可以很方便的創(chuàng)建線程池: newCachedThreadPool New threads are created as needed; idle threads are kept for 60 seconds. newFixedThreadPool The pool contains a fixed set of threads; idle threads are kept indefinitely. newSingleThreadExecutor A "pool" with a single thread that executes the submitted tasks sequentially. newScheduledThreadPool A fixed-thread pool for scheduled execution. newSingleThreadScheduledExecutor A single-thread "pool" for scheduled execution. 在使用Executors時,先調(diào)用這些靜態(tài)方法創(chuàng)建線程池,得到一個ExecutorService對象,然后用這個對象的submit方法提交你的Runnable或是Callable對象。 Future<?> submit(Runnable task) Future<T> submit(Runnable task, T result) Future<T> submit(Callable<T> task) 如果不再需要任何提交,就用shutdown方法來關(guān)閉線程池。 10.在界面中使用多線程 對于GUI設(shè)計來說,很重要的一個原則就是要及時的給用戶反饋,就算是不能立即得到結(jié)果,界面也不能停在那里,是用戶不知道發(fā)生了什么事情,必須讓用戶隨時知道程序在坐什么。所以當程序要執(zhí)行一段需要消耗比較長時間的操作時,就要使用多線程。 但是,有些界面控件并不是線程安全的,在使用這些控件時就要特別注意。在API doc中這些都有注明,使用的時候就可以查一下。 如果想在自己另外所創(chuàng)建的線程執(zhí)行過程中隨時更新界面來表示執(zhí)行進程,要注意的一點是,這個線程并不能直接調(diào)用界面控件的方法,而要采用EventQueue類的invokeLater,invokeAndWait方法: EventQueue.invokeLater(new Runnable() { public void run() { label.setText(percentage + "% complete"); } }); |