進程:它是內存中的一段獨立的空間。java
線程:位於進程中,負責當前進程中的某個具有獨立運行資格的空間。編程
進程是負責整個程序的運行,而線程是程序中具體的某個獨立功能的運行。一個進程中至少應該有一個線程。數組
多線程:在一個進程中,咱們同時開啓多個線程,讓多個線程同時去完成某些任務(功能)。(好比後臺服務系統,就能夠用多個線程同時響應多個客戶的請求)數據結構
多線程的目的:提升程序的運行效率。多線程
多線程運行的原理:CPU負責程序的運行,而CPU在運行程序的過程當中某個時刻點上,它其實只能運行一個程序。CPU它能夠在多個程序之間進行高速的切換。併發
每一個程序就是進程, 而每一個進程中會有多個線程,而CPU是在這些線程之間進行切換。多線程能夠提升程序的運行效率,但不能無限制的開線程。框架
實現多線程的兩種方式:一、繼承Thread;二、實現Runnable接口。異步
Java同步機制:函數
一、synchronized工具
synchronized( 須要一個任意的對象(鎖) ){
代碼塊中放操做共享數據的代碼。
}
synchronized缺陷:
一、多線程阻塞:須要有一種機制能夠不讓等待的線程一直無期限地等待下去(好比只等待必定的時間或者可以響應中斷),經過Lock就能夠辦到。
二、多線程讀寫文件:須要一種機制來使得多個線程都只是進行讀操做時,線程之間不會發生衝突,經過Lock就能夠辦到。
經過Lock能夠知道線程有沒有成功獲取到鎖。這個是synchronized沒法辦到的。
二、Lock
Lock和synchronized的區別:
一、Lock不是Java語言內置的,synchronized是Java語言的關鍵字,所以是內置特性。Lock是一個類,經過這個類能夠實現同步訪問;
二、Lock和synchronized有一點很是大的不一樣,採用synchronized不須要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完以後,系統會自動讓線程釋放對鎖的佔用;而Lock則必需要用戶去手動釋放鎖,若是沒有主動釋放鎖,就有可能致使出現死鎖現象。
java.util.concurrent.locks包下經常使用的類:
一、Lock
public interface Lock {
void lock();//獲取鎖
void lockInterruptibly() throws InterruptedException;//獲取鎖
boolean tryLock();//獲取鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//獲取鎖
void unlock();//釋放鎖
}
因爲在前面講到若是採用Lock,必須主動去釋放鎖,而且在發生異常時,不會自動釋放鎖。所以通常來講,使用Lock必須在try{}catch{}塊中進行,而且將釋放鎖的操做放在finally塊中進行,以保證鎖必定被被釋放,防止死鎖的發生。
二、ReentrantLock
三、ReadWriteLock
四、ReentrantReadWriteLock
注意:
不過要注意的是,若是有一個線程已經佔用了讀鎖,則此時其餘線程若是要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。
若是有一個線程已經佔用了寫鎖,則此時其餘線程若是申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。
五、Lock和synchronized的選擇
1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;
2)synchronized在發生異常時,會自動釋放線程佔有的鎖,所以不會致使死鎖現象發生;而Lock在發生異常時,若是沒有主動經過unLock()去釋放鎖,則極可能形成死鎖現象,所以使用Lock時須要在finally塊中釋放鎖;
3)Lock可讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不可以響應中斷;
4)經過Lock能夠知道有沒有成功獲取鎖,而synchronized卻沒法辦到。
5)Lock能夠提升多個線程進行讀操做的效率。
在性能上來講,若是競爭資源不激烈,二者的性能是差很少的,而當競爭資源很是激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。因此說,在具體使用時要根據適當狀況選擇。
Java併發
線程池的5中建立方式:
一、 Single Thread Executor : 只有一個線程的線程池,所以全部提交的任務是順序執行,
代碼: Executors.newSingleThreadExecutor()
二、 Cached Thread Pool : 線程池裏有不少線程須要同時執行,老的可用線程將被新的任務觸發從新執行,若是線程超過60秒內沒執行,那麼將被終止並從池中刪除,
代碼:Executors.newCachedThreadPool()
三、 Fixed Thread Pool : 擁有固定線程數的線程池,若是沒有任務執行,那麼線程會一直等待,
代碼: Executors.newFixedThreadPool(4)
在構造函數中的參數4是線程池的大小,你能夠隨意設置,也能夠和cpu的核數量保持一致,獲取cpu的核數量int cpuNums = Runtime.getRuntime().availableProcessors();
四、 Scheduled Thread Pool : 用來調度即將執行的任務的線程池,多是不是直接執行, 每隔多久執行一次... 策略型的
代碼:Executors.newScheduledThreadPool()
五、 Single Thread Scheduled Pool : 只有一個線程,用來調度任務在指定時間執行,代碼:Executors.newSingleThreadScheduledExecutor()
線程池的使用
提交 Runnable ,任務完成後 Future 對象返回 null
調用excute,提交任務, 匿名Runable重寫run方法, run方法裏是業務邏輯
提交 Callable,該方法返回一個 Future 實例表示任務的狀態
調用submit提交任務, 匿名Callable,重寫call方法, 有返回值, 獲取返回值會阻塞,一直要等到線程任務返回結果
java併發包消息隊列及在開源軟件中的應用
BlockingQueue也是java.util.concurrent下的主要用來控制線程同步的工具。
主要的方法是:put、take一對阻塞存取;add、poll一對非阻塞存取。
插入:
1)add(anObject):把anObject加到BlockingQueue裏,即若是BlockingQueue能夠容納,則返回true,不然拋出異常,很差
2)offer(anObject):表示若是可能的話,將anObject加到BlockingQueue裏,即若是BlockingQueue能夠容納,則返回true,不然返回false.
3)put(anObject):把anObject加到BlockingQueue裏,若是BlockQueue沒有空間,則調用此方法的線程被阻斷直到BlockingQueue裏面有空間再繼續, 有阻塞, 放不進去就等待
讀取:
4)poll(time):取走BlockingQueue裏排在首位的對象,若不能當即取出,則能夠等time參數規定的時間,取不到時返回null; 取不到返回null
5)take():取走BlockingQueue裏排在首位的對象,若BlockingQueue爲空,阻斷進入等待狀態直到Blocking有新的對象被加入爲止; 阻塞, 取不到就一直等
其餘
int remainingCapacity();返回隊列剩餘的容量,在隊列插入和獲取的時候,不要瞎搞,數 據可能不許, 不能保證數據的準確性
boolean remove(Object o); 從隊列移除元素,若是存在,即移除一個或者更多,隊列改 變了返回true
public boolean contains(Object o); 查看隊列是否存在這個元素,存在返回true
int drainTo(Collection<? super E> c); //移除此隊列中全部可用的元素,並將它們添加到給定 collection 中。取出放到集合中
int drainTo(Collection<? super E> c, int maxElements); 和上面方法的區別在於,指定了移 動的數量; 取出指定個數放到集合
BlockingQueue有四個具體的實現類,經常使用的兩種實現類爲:
1、ArrayBlockingQueue:一個由數組支持的有界阻塞隊列,規定大小的BlockingQueue,其構造函數必須帶一個int參數來指明其大小.其所含的對象是以FIFO(先入先出)順序排序的。
2、LinkedBlockingQueue:大小不定的BlockingQueue,若其構造函數帶一個規定大小的參數,生成的BlockingQueue有大小限制,若不帶大小參數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定.其所含的對象是以FIFO(先入先出)順序排序的。
LinkedBlockingQueue 能夠指定容量,也能夠不指定,不指定的話,默認最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來。
LinkedBlockingQueue和ArrayBlockingQueue區別:
LinkedBlockingQueue和ArrayBlockingQueue比較起來,它們背後所用的數據結構不同,致使LinkedBlockingQueue的數據吞吐量要大於ArrayBlockingQueue,但在線程數量很大時其性能的可預見性低於ArrayBlockingQueue.
java併發編程的一些總結
.1. 不該用線程池的缺點
有些開發者圖省事,遇到須要多線程處理的地方,直接new Thread(...).start(),對於通常場景是沒問題的,但若是是在併發請求很高的狀況下,就會有些隱患:
.2. 制定執行策略
在每一個須要多線程處理的地方,無論併發量有多大,須要考慮線程的執行策略
.3. 線程池的類型
不論是經過Executors建立線程池,仍是經過Spring來管理,都得清楚知道有哪幾種線程池:
其實,這些不一樣類型的線程池都是經過構建一個ThreadPoolExecutor來完成的,所不一樣的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory這麼幾個參數。具體能夠參見JDK DOC。
.4. 線程池飽和策略
由以上線程池類型可知,除了CachedThreadPool其餘線程池都有飽和的可能,當飽和之後就須要相應的策略處理請求線程的任務,好比,達到上限時經過ThreadPoolExecutor.setRejectedExecutionHandler方法設置一個拒絕任務的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy幾種策略,具體差別可見JDK DOC
.5. 線程無依賴性
多線程任務設計上儘可能使得各任務是獨立無依賴的,所謂依賴性可兩個方面:
固然,在有些業務裏確實須要必定的依賴性,好比調用者須要獲得線程完成後結果,傳統的Thread是不便完成的,由於run方法無返回值,只能經過一些共享的變量來傳遞結果,但在Executor框架裏能夠經過Future和Callable實現須要有返回值的任務,固然線程的異步性致使須要有相應機制來保證調用者能等待任務完成。