thread執行緒及其相關知識
Thread的相關知識
程序介紹
不管是我們開發的應用程式,還是我們執行的其他的應用程式,都需要先把程式安裝在本地的硬碟上。然後找到這個程式的啟動檔案,啟動程式的時候,其實是電腦把當前的這個程式載入到記憶體中,在記憶體中需要給當前的程式分配一段獨立的執行空間。這片空間就專門負責當前這個程式的執行。
不同的應用程式執行的過程中都需要在記憶體中分配自己獨立的執行空間,彼此之間不會相互的影響。我們把每個獨立應用程式在記憶體的獨立空間稱為當前應用程式執行的一個程序。
程序是作業系統中執行的一個任務(一個應用程式執行在一個程序中)。
程序(process)是一塊包含某些資源的記憶體區域。作業系統利用程序把它的工作劃分為一些功能單元。
程序中所包含的一個或多個執行單元稱為執行緒(thread)。程序還擁有一個私有的虛擬地址空間,該空間僅能被它所包含的執行緒訪問。
程序
執行緒介紹
在一個程序中,每個獨立的功能都需要獨立的去執行,這時又需要把當前這個程序劃分成多個執行區域,每個獨立的小區域(小單元)稱為一個執行緒。
執行緒:它是位於程序中,負責當前程序中的某個具備獨立執行資格的空間。
程序是負責整個程式的執行,而執行緒是程式中具體的某個獨立功能的執行。一個程序中至少應該有一個執行緒。
一個執行緒是程序的一個順序執行流。
同類的多個執行緒共享一塊記憶體空間和一組系統資源,執行緒本身有一個供執行時的堆疊。執行緒在切換時負荷小,因此,執行緒也被稱為輕負荷程序。一個程序中可以包含多個執行緒。
多執行緒介紹
現在的作業系統基本都是多使用者,多工的作業系統。每個任務就是一個程序。而在這個程序中就會有執行緒。
真正可以完成程式執行和功能的實現靠的是程序中的執行緒。
多執行緒:在一個程序中,我們同時開啟多個執行緒,讓多個執行緒同時去完成某些任務(功能)。
多執行緒的目的:提高程式的執行效率。
多執行緒執行的原理
實際上是cpu線上程中做時間片的切換。
其實真正電腦中的程式的執行不是同時在執行的。CPU負責程式的執行,而CPU在執行程式的過程中某個時刻點上,它其實只能執行一個程式。而不是多個程式。而CPU它可以在多個程式之間進行高速的切換。而切換頻率和速度太快,導致人的肉看看不到。
每個程式就是程序, 而每個程序中會有多個執行緒,而CPU是在這些執行緒之間進行切換。
瞭解了CPU對一個任務的執行過程,我們就必須知道,多執行緒可以提高程式的執行效率,但不能無限制的開執行緒。
:它是記憶體中的一段獨立的空間,可以負責當前應用程式的執行。當前這個程序負責排程當前程式中的所有執行細節。
實現執行緒的兩種方式
1、繼承Thread的原理
public class MyThreadWithExtends extends Thread {
String flag;
public MyThreadWithExtends(String flag){
this.flag = flag;
}
@Override
public void run() {
String tname = Thread.currentThread().getName();
System.out.println(tname+"執行緒的run方法被呼叫");
Random random = new Random();
for(int i=0;i<20;i++){
try {
Thread.sleep(random.nextInt(10)*100);
System.out.println(tname+ "...."+ flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread1 = new MyThreadWithExtends("a");
Thread thread2 = new MyThreadWithExtends("b");
thread1.start();
thread2.start();
/**
* 如果是呼叫thread的run方法,則只是一個普通的方法呼叫,不會開啟新的執行緒?
*/
// thread1.run();
// thread2.run();
}
}
2、宣告實現 Runnable 介面的類
public class MyThreadWithImpliment implements Runnable {
int x;
public MyThreadWithImpliment(int x) {
this.x = x;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("執行緒" + name + "的run方法被呼叫");
for (int i = 0; i < 10; i++) {
System.out.println(x);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(new MyThreadWithImpliment(1), "thread-1");
Thread thread2 = new Thread(new MyThreadWithImpliment(2), "thread-2");
thread1.start();
thread2.start();
// 注意呼叫run和呼叫start的區別?,直接呼叫run,則都執行在main執行緒上?
// thread1.run();
// thread2.run();
}
}
java同步關鍵詞解釋
- Synchronized
加同步格式:
synchronized( 需要一個任意的物件(鎖) ){
程式碼塊中放操作共享資料的程式碼。
}
public class MySynchronized {
public static void main(String[] args) {
final MySynchronized mySynchronized = new MySynchronized();
final MySynchronized mySynchronized2 = new MySynchronized();
new Thread("thread1") {
public void run() {
synchronized (mySynchronized) {
try {
System.out.println(this.getName()+" start");
int i =1/0; //如果發生異常,jvm會將鎖釋放?
Thread.sleep(5000);
System.out.println(this.getName()+"醒了");
System.out.println(this.getName()+" end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread("thread2") {
public void run() {
synchronized (mySynchronized) { //爭搶同一把鎖時,執行緒1沒釋放之前,執行緒2只能等待
// synchronized (mySynchronized2) { //如果不是同一把鎖,可以看到兩句話同時列印
System.out.println(this.getName()+" start");
System.out.println(this.getName()+" end");
}
}
}.start();
}
}
- synchronized的缺陷
synchronized是java中的一個關鍵字,也就是說是Java語言內建的特性。
如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況:
1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有;
2)執行緒執行發生異常,此時JVM會讓執行緒自動釋放鎖。
例子1:
如果這個獲取鎖的執行緒由於要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能乾巴巴地等待,試想一下,這多麼影響程式執行效率。
因此就需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。
例子2:
當有多個執行緒讀寫檔案時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。
但是採用synchronized關鍵字來實現同步的話,就會導致一個問題:
如果多個執行緒都只是進行讀操作,當一個執行緒在進行讀操作時,其他執行緒只能等待無法進行讀操作。
因此就需要一種機制來使得多個執行緒都只是進行讀操作時,執行緒之間不會發生衝突,通過Lock就可以辦到。
另外,通過Lock可以知道執行緒有沒有成功獲取到鎖。這個是synchronized無法辦到的。
總的來說,也就是說Lock提供了比synchronized更多的功能。
2.2 lock
- lock和synchronized的區別
1)Lock不是Java語言內建的,synchronized是Java語言的關鍵字,因此是內建特性。Lock是一個類,通過這個類可以實現同步訪問;
2)Lock和synchronized有一點非常大的不同,採用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized程式碼塊執行完之後,系統會自動讓執行緒釋放對鎖的佔用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。
- java.util.concurrent.locks包下常用的類
- Lock
- 首先要說明的就是Lock,通過檢視Lock的原始碼可知,Lock是一個介面:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
}
Lock介面中每個方法的使用:
lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用來獲取鎖的。 unLock()方法是用來釋放鎖的。
四個獲取鎖方法的區別:
lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行等待。
由於在前面講到如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。
tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒能夠響應中斷,即中斷執行緒的等待狀態。也就使說,當兩個執行緒同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有在等待,那麼對執行緒B呼叫threadB.interrupt()方法能夠中斷執行緒B的等待過程。
注意,當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。
因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。
而用synchronized修飾的話,當一個執行緒處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。
注意:
不過要注意的是,如果有一個執行緒已經佔用了讀鎖,則此時其他執行緒如果要申請寫鎖,則申請寫鎖的執行緒會一直等待釋放讀鎖。
如果有一個執行緒已經佔用了寫鎖,則此時其他執行緒如果申請寫鎖或者讀鎖,則申請的執行緒會一直等待釋放寫鎖。
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併發包
java併發包介紹
JDK5.0 以後的版本都引入了高階併發特性,大多數的特性在java.util.concurrent 包中,是專門用於多執行緒發程式設計的,充分利用了現代多處理器和多核心繫統的功能以編寫大規模併發應用程式。主要包含原子量、併發集合、同步器、可重入鎖,並對執行緒池的構造提供
了強力的支援。
執行緒池
執行緒池的5中建立方式:
- Single Thread Executor : 只有一個執行緒的執行緒池,因此所有提交的任務是順序執行,
程式碼: Executors.newSingleThreadExecutor()
Cached Thread Pool : 執行緒池裡有很多執行緒需要同時執行,老的可用執行緒將被新的任務觸發重新執行,如果執行緒超過60秒內沒執行,那麼將被終止並從池中刪除,
/**
* 通過執行緒池執行執行緒
* @param args
*/
public static void main(String[] args) {
//建立一個執行緒池
ExecutorService pool = Executors.newCachedThreadPool();
for(int i = 1; i < 5; i++){
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("thread name:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
pool.shutdown();
}
}
Fixed Thread Pool : 擁有固定執行緒數的執行緒池,如果沒有任務執行,那麼執行緒會一直等待,
public class ThreadPoolWithcallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(4);
for(int i = 0; i < 10; i++){
Future<String> submit = pool.submit(new Callable<String>(){
@Override
public String call() throws Exception {
//System.out.println("a");
Thread.sleep(5000);
return "b--"+Thread.currentThread().getName();
}
});
//從Future中get結果,這個方法是會被阻塞的,一直要等到執行緒任務返回結果
System.out.println(submit.get());
}
pool.shutdown();
}
}
在建構函式中的引數4是執行緒池的大小,你可以隨意設定,也可以和cpu的數量保持一致,獲取cpu的數量int cpuNums = Runtime.getRuntime().availableProcessors();
Scheduled Thread Pool : 用來排程即將執行的任務的執行緒池,
程式碼:Executors.newScheduledThreadPool()
Single Thread Scheduled Pool : 只有一個執行緒,用來排程執行將來的任務,程式碼:Executors.newSingleThreadScheduledExecutor()
/**
* 列出併發包中的各種執行緒池
* @author
*
*/
public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
int cpuNums = Runtime.getRuntime().availableProcessors();
System.out.println(cpuNums);
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(cpuNums);
ScheduledExecutorService newScheduledThreadPool =
Executors.newScheduledThreadPool(8);
ScheduledExecutorService newSingleThreadScheduledExecutor =
Executors.newSingleThreadScheduledExecutor();
}
}
執行緒池的使用
提交 Runnable ,任務完成後 Future 物件返回 null
public class ThreadPoolWithRunable {
/**
* 通過執行緒池執行執行緒?
* @param args
*/
public static void main(String[] args) {
//建立一個執行緒池
ExecutorService pool = Executors.newCachedThreadPool();
for(int i = 1; i < 5; i++){
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("thread name:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
pool.shutdown();
}
}
提交 Callable,該方法返回一個 Future 例項表示任務的狀態
/**
* callable 跟runnable的區別:
runnable的run方法不會有任何返回結果,它是以主執行緒無法獲得任務執行緒的返回結果
* callable的call方法可以返回結果,但是主執行緒在獲取時是被阻塞
* 要等待任務執行緒返回才能拿到結果
*/
public class ThreadPoolWithcallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(4);
for(int i = 0; i < 10; i++){
Future<String> submit = pool.submit(new Callable<String>(){
@Override
public String call() throws Exception {
//System.out.println("a");
Thread.sleep(5000);
return "b--"+Thread.currentThread().getName();
}
});
//從Future中get結果,這個方法是會被阻塞的,一直要等到執行緒任務返回結果
System.out.println(submit.get());
}
pool.shutdown();
}
}
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
5)take():取走BlockingQueue裡排在首位的物件,若BlockingQueue為空,阻斷進入等待狀態直到Blocking有新的物件被加入為止
其他
int remainingCapacity();返回佇列剩餘的容量,在佇列插入和獲取的時候,不要瞎搞,數 據可能不準
boolean remove(Object o); 從佇列移除元素,如果存在,即移除一個或者更多,佇列改 變了返回true
public boolean contains(Object o); 檢視佇列是否存在這個元素,存在返回true
int drainTo(Collection<? super E> c); 傳入的集合中的元素,如果在佇列中存在,那麼將 佇列中的元素移動到集合中
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.