掌握Java中的多線程,必須掌握Java中的各種鎖,以及了解Java中線程池的運用。關於Java多線程基礎總結可以參考我的這篇博文Java多線程總結(一)多線程基礎
轉載請注明出處——http://www.cnblogs.com/zrtqsk/p/3784049.html,謝謝。
一、Java中鎖
什么是鎖。鎖就是為了保護資源,防止多個線程同時操作資源時出錯的機制。
我們先來看一下鎖的類圖:
如圖,Java中的鎖有兩個主要的根接口——Lock和ReadWriteLock,分別表示鎖和讀寫鎖。其中Lock的主要實現類是ReetrantLock。ReadWriteLock的主要實現類是ReetrantReadWriteLock。而ReetrantReadWriteLock讀寫鎖是通過兩個內部類——ReadLock和WriteLock實現的,其中ReadLock是共享鎖,WriteLock是獨占鎖。這兩個內部類都實現了Lock接口。
(1)、Java中的鎖主要有以下幾種概念:
1、同步鎖
同一時刻,一個同步鎖只能被一個線程訪問。以對象為依據,通過synchronized關鍵字來進行同步,實現對競爭資源的互斥訪問。
2、獨占鎖(可重入的互斥鎖)
互斥,即在同一時間點,只能被一個線程持有;可重入,即可以被單個線程多次獲取。什么意思呢?根據鎖的獲取機制,它分為“公平鎖”和“非公平鎖”。Java中通過ReentrantLock實現獨占鎖,默認為非公平鎖。
3、公平鎖
是按照通過CLH等待線程按照先來先得的規則,線程依次排隊,公平的獲取鎖,是獨占鎖的一種。Java中,ReetrantLock中有一個Sync類型的成員變量sync,它的實例為FairSync類型的時候,ReetrantLock為公平鎖。設置sync為FairSync類型,只需——Lock lock = new ReetrantLock(true)。
4、非公平鎖
是當線程要獲取鎖時,它會無視CLH等待隊列而直接獲取鎖。ReetrantLock默認為非公平鎖,或——Lock lock = new ReetrantLock(false)。
5、共享鎖
能被多個線程同時獲取、共享的鎖。即多個線程都可以獲取該鎖,對該鎖對象進行處理。典型的就是讀鎖——ReentrantReadWriteLock.ReadLock。即多個線程都可以讀它,而且不影響其他線程對它的讀,但是大家都不能修改它。CyclicBarrier, CountDownLatch和Semaphore也都是共享鎖。
6、讀寫鎖
維護了一對相關的鎖,“讀取鎖”用於只讀操作,它是“共享鎖”,能同時被多個線程獲取。“寫入鎖”用於寫入操作,它是“獨占鎖”,只能被一個線程鎖獲取。Java中,讀寫鎖為ReadWriteLock 接口定義,其實現類是ReentrantReadWriteLock,包括內部類ReadLock和WriteLock。方法readLock()、writeLock()分別返回度操作的鎖和寫操作的鎖。
(至於“死鎖”,並不是一種鎖,而是一種狀態,即兩個線程互相等待對方釋放同步監視器的時候,雙方都無法繼續進行,造成死鎖。)
鎖的用法主要就是下面的流程:
//先得到lock lock.lock();//然后獲取鎖 try { //各種控制操作 }catch(Exception e){ }finally { lock.unlock();//解鎖 }
可以看到,這樣的用法比synchronized關鍵字依據對象同步,要方便簡單的多。
(2)LockSupport和Condition
1、LockSupport
是用來創建鎖和其他同步類的基本線程阻塞原語。 LockSupport中的靜態方法park() 和 unpark() 的作用分別是阻塞線程和解除阻塞線程,而不會導致死鎖。演示如下:
package lock; import java.util.concurrent.locks.LockSupport; public class LockSupportTest { static Thread mainThread = null; public static void main(String[] args) { //獲取主線程 mainThread = Thread.currentThread(); //新建線程並啟動 MyThread thread1 = new MyThread("thread1"); thread1.start(); //模擬線程工作開始 System.out.println(Thread.currentThread().getName() + "-----》 runs now!"); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "-----》 running step " + i); //當前線程睡眠1秒 sleepOneSecond(); if(i == 2){ System.out.println(Thread.currentThread().getName() + "-----》 now pack main thread——————————"); //讓主線程阻塞 LockSupport.park(); } } System.out.println(Thread.currentThread().getName() + "-----》 run over!"); } /**當前線程暫停一秒鍾 */ public static void sleepOneSecond(){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } static class MyThread extends Thread { public MyThread(String name){ super(name); } @Override public void run() { synchronized (this) { //模擬工作開始 System.out.println(Thread.currentThread().getName() + "-----》 runs now!"); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "-----》 running step " + i); //當前線程睡眠1秒 sleepOneSecond(); } //模擬工作結束 System.out.println(Thread.currentThread().getName() + "-----》 run over!"); } System.out.println(Thread.currentThread().getName() + "-----》 now unpack main thread———————— "); //解除主線程的阻塞 LockSupport.unpark(mainThread); } } }
結果如下:
thread1-----》 runs now! thread1-----》 running step 0 main-----》 runs now! main-----》 running step 0 thread1-----》 running step 1 main-----》 running step 1 main-----》 running step 2 thread1-----》 running step 2 main-----》 now pack main thread—————————— thread1-----》 running step 3 thread1-----》 running step 4 thread1-----》 run over! thread1-----》 now unpack main thread———————— main-----》 running step 3 main-----》 running step 4 main-----》 run over!
2、Condition
對鎖進行精確的控制,可用來代替Object中的wait、notify、notifyAll方法,需要和Lock聯合使用。可以通過await(),signal()來休眠、喚醒線程。創建方式:Condition condition = lock.newCondition();
演示懶得自己寫了,參照http://www.cnblogs.com/skywang12345/p/3496716.html,如下:
package LockSupportTest; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionTest { private static Lock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); public static void main(String[] args) { ThreadA ta = new ThreadA("ta"); lock.lock(); // 獲取鎖 try { System.out.println(Thread.currentThread().getName()+" start ta"); ta.start(); System.out.println(Thread.currentThread().getName()+" block"); condition.await(); // 等待 System.out.println(Thread.currentThread().getName()+" continue"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); // 釋放鎖 } } static class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { lock.lock(); // 獲取鎖 try { System.out.println(Thread.currentThread().getName()+" wakup others"); condition.signal(); // 喚醒“condition所在鎖上的其它線程” } finally { lock.unlock(); // 釋放鎖 } } } }
結果如下:
main start ta main block ta wakup others main continue
如上,用起來挺簡單的。
二、線程池
我們先來看一下線程池的類圖:
1、介紹
可見,線程池的主要是由一個Executor接口統籌的。這個接口代表一個執行者,是一個典型的命令模式的運用。這個接口只有一個方法void execute(Runnable command),提交並執行任務。
ExecuteService顧名思義,指的是Executor的服務類,繼承了Executor接口,提供了更詳細的控制線程的方法。
AbstractExecutorService是一個抽象類,實現了ExecutorService大部分的方法。
而我們最常用的ThreadPoolExecutor則繼承了ExecutorService。
ForkJoinPool是JDK7新增的線程池,也是繼承了這個線程類。
ScheduledExecutorService這個接口繼承了ExecutorService,比ExecutorService新增了“延時”和“周期執行”的功能。
ScheduledThreadPoolExecutor這個類則實現了ScheduledExecutorService接口,且繼承了ThreadPoolExecutor,新增了“延時”和“周期執行”的功能。
Executors是一個線程池的工廠類,提供一系列靜態方法,用於創建各種不同功能的線程池或線程相關的對象。
而線程池的使用,最基本的就是如下:
// 創建各種線程 Thread thread1 = new MyThread(); Thread thread2 = new MyThread(); Thread thread3 = new MyThread(); // 創建線程池pool // 將線程放入池中進行執行 pool.execute(thread1 ); pool.execute(thread2 ); pool.execute(thread3 ); // 關閉線程池 pool.shutdown();
2、ForkJoinPool
可見,整個線程池系列,說白了,也就3個類ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。一個是普通線程池,一個是新增了“延時”和“周期執行”的功能的線程池。那么ForkJoinPool是什么呢?
ForkJoinPool為了是解決現在、未來計算機多核的問題。ExecuteService其他實現類基本都是基於單核下執行的,解決的是並發問題,而ForkJoinPool解決的是並行問題。ExcuteService中處於后面的任務需要等待前面任務執行后才有機會執行,而ForkJoinPool會采用work-stealing模式幫助其他線程執行任務。work-stealing模式——所有在池中的線程嘗試去執行其他線程創建的子任務,這樣就很少有線程處於空閑狀態,非常高效。
ForkJoinPool除了可以執行Runnable任務外,還可以執行ForkJoinTask任務,即ForkJoinPool的execute方法可以傳入一個ForkJoinTask對象,這個任務對象跟Runnable的不同是,ForkJoinTask被放到線程內部的隊列里面,而普通的Runnable任務被放到線程池的隊列里面了。
需要詳細了解ForkJoinPool,可以參考http://blog.csdn.net/aesop_wubo/article/details/10300273。
3、Executors
Executors是一個線程池的工廠類,提供一系列靜態方法,用於創建各種不同功能的線程池或線程相關的對象。
主要有如下的幾個靜態方法:
newCachedThreadPool() : 創建一個具有緩存功能的線程池,系統根據需要創建線程,這些線程被緩存在線程池中。
newFixedThreadPool(int nThreads) : 創建一個可重用的,具有固定線程數的線程池。
newSingleThreadExecutor() : 創建一個只有一個單線程的線程池。
newScheduledThreadPool(int corePoolSize) : 創建具有指定數目的線程池,可以指定延時后執行任務,即使線程空閑,也被保持在線程池內。
newSingleThreadScheduledExecutor() : 創建一個只有一個單線程的線程池,可以指定延時后執行任務。
4、線程池的狀態
線程池的狀態有五種——RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED
(圖片出處:http://www.cnblogs.com/skywang12345/p/3509960.html)
RUNNING : 線程池處在RUNNING狀態時,能夠接收新任務,以及對已添加的任務進行處理。
SHUTDOWN : 線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務。
STOP : 線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,並且會中斷正在處理的任務。
TIDYING : 當所有的任務已終止,ctl記錄的"任務數量"為0,線程池會變為TIDYING狀態。 當線程池變為TIDYING狀態時,會執行鈎子函數terminated()。terminated()在ThreadPoolExecutor類中是空 的,若用戶想在線程池變為TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。
TERMINATED : 線程池徹底終止,就變成TERMINATED狀態。
參考:http://www.cnblogs.com/skywang12345/p/java_threads_category.html
http://blog.csdn.net/aesop_wubo/article/details/10300273
如果覺得本文還不錯的話,麻煩點擊推薦哦!謝謝啦!