一 常用概念與特性
1 并發(fā)與并行的概念
并行:多個(gè)cpu實(shí)例或者多臺(tái)機(jī)器同時(shí)執(zhí)行一段處理邏輯,是真正的同時(shí)。
并發(fā):通過cpu調(diào)度算法,讓用戶看上去同時(shí)執(zhí)行,實(shí)際上從cpu操作層面不是真正的同時(shí)。
2 線程安全與不安全的概念
線程安全:經(jīng)常用來描繪一段代碼。指在并發(fā)的情況之下,該代碼經(jīng)過多線程使用,線程的調(diào)度順序不影響任何結(jié)果。這個(gè)時(shí)候使用多線程,我們只需要關(guān)注系統(tǒng)的內(nèi)存,cpu是不是夠用即可。反過來,線程不安全就意味著線程的調(diào)度順序會(huì)影響最終結(jié)果,如不加事務(wù)的轉(zhuǎn)賬代碼。
3 同步的概念
同步:Java中的同步指的是通過人為的控制和調(diào)度,保證共享資源的多線程訪問成為線程安全,來保證結(jié)果的準(zhǔn)確。如一些代碼簡單的加入@synchronized關(guān)鍵字保證結(jié)果的準(zhǔn)確性。在保證結(jié)果準(zhǔn)確的同時(shí),提高性能,才是優(yōu)秀的程序。線程安全的優(yōu)先級高于性能。
4 線程的各種狀態(tài)


線程的六大狀態(tài)(一個(gè)線程在一個(gè)時(shí)刻只能處于一個(gè)狀態(tài))
1) NEW(new新建狀態(tài)):
當(dāng)一個(gè)線程創(chuàng)建好后,沒有調(diào)用start方法時(shí)所處于的狀態(tài)。
2) RUNNABLE(runnable就緒狀態(tài)):
當(dāng)線程調(diào)用了start方法后,該線程就會(huì)被放在可運(yùn)行的線程池中,去爭取CPU的時(shí)間片。
3) BLOCKED(blocked):
處于阻塞狀態(tài)的線程在等待monitor鎖。
4) WAITING(waiting):
一個(gè)線程在等待另一個(gè)線程執(zhí)行一個(gè)動(dòng)作時(shí)在這個(gè)狀態(tài)。
當(dāng)該線程調(diào)用wait()方法或者其他線程在該線程里面調(diào)用join()時(shí),線程所處于的狀態(tài)(注意,這兩個(gè)方法都沒有參數(shù))。處于該狀態(tài)的線程如果沒有被喚醒,將處于無限等待的狀態(tài)。
wait():
5) TIMED_WAITING(timed_waiting):
一個(gè)線程在一個(gè)特定的等待時(shí)間內(nèi)等待另一個(gè)線程完成一個(gè)動(dòng)作會(huì)在這個(gè)狀態(tài)。
當(dāng)該線程調(diào)用sleep方法或者調(diào)用wait(long)方法,或者其他線程在里面調(diào)用join(long)方法時(shí),線程所處于的狀態(tài)。該狀態(tài)雖然會(huì)等待,但設(shè)置了時(shí)間限制,不會(huì)無限等待,當(dāng)超過時(shí)間以后會(huì)自動(dòng)喚醒。
6) TERMINATED(terminated):
線程運(yùn)行完后的狀態(tài)。
5 線程狀態(tài)之間的轉(zhuǎn)換
當(dāng)一個(gè)線程創(chuàng)建以后,就處于新建狀態(tài)。那什么時(shí)候這個(gè)狀態(tài)會(huì)改變呢?只要它調(diào)用的start()方法,線程就進(jìn)入了鎖池狀態(tài)。
進(jìn)入鎖池以后就會(huì)參與鎖的競爭,當(dāng)它獲得鎖以后還不能馬上運(yùn)行,因?yàn)橐粋€(gè)單核CPU在某一時(shí)刻,只能執(zhí)行一個(gè)線程,所以他需要操作系統(tǒng)分配給它時(shí)間片,才能執(zhí)行。所以人們通常把一個(gè)線程在獲得鎖后,獲得系統(tǒng)時(shí)間片之前的狀態(tài)稱之為可運(yùn)行狀態(tài)(就緒狀態(tài)),但Java源代碼里面并沒有可運(yùn)行狀態(tài)這一說。
當(dāng)一個(gè)持有對象鎖的線程獲得CPU時(shí)間片以后,開始執(zhí)行這個(gè)線程,此時(shí)叫做運(yùn)行狀態(tài)。
當(dāng)一個(gè)線程正常執(zhí)行完,那么就進(jìn)入終止(死亡)狀態(tài)。系統(tǒng)就會(huì)回收這個(gè)線程占用的資源。
但是,線程的執(zhí)行并不是那么順利的。一個(gè)正在運(yùn)行的線程有可能會(huì)進(jìn)入I/O交互,還可能調(diào)用sleep()方法,還有可能在當(dāng)前線程當(dāng)中有其它線程調(diào)用了join()方法。這時(shí)候線程就進(jìn)入了阻塞狀態(tài)(但這也只是我們在理解的時(shí)候自己加上去的,源代碼里也沒有定義這一個(gè)狀態(tài))。阻塞狀態(tài)的線程是沒有釋放對象鎖的。當(dāng)I/O交互完成,或sleep()方法完成,或其它調(diào)用join()方法的線程執(zhí)行完畢,阻塞狀態(tài)的線程就會(huì)恢復(fù)到可運(yùn)行狀態(tài),此時(shí)如果再次獲得CPU時(shí)間片就會(huì)進(jìn)入運(yùn)行狀態(tài)。
一個(gè)處于運(yùn)行狀態(tài)的線程還可能調(diào)用wait()方法、或者帶時(shí)間參數(shù)的wait(long milli)方法。這時(shí)候線程就會(huì)將對象鎖釋放,進(jìn)入等待隊(duì)列里面(如果是調(diào)用wait()方法則進(jìn)入等待狀態(tài),如果是調(diào)用帶時(shí)間參數(shù)的則進(jìn)入定時(shí)等待狀態(tài))。
一個(gè)線程如果的調(diào)用了帶時(shí)間參數(shù)的wait(long milli)方法進(jìn)入了定時(shí)等待狀態(tài),那么只要時(shí)間一到就會(huì)進(jìn)入鎖池狀態(tài),并不需要notify()或notifyAll()方法來喚醒它。如果調(diào)用的是不帶時(shí)間參數(shù)的wait()則需要notify()或notifyAll()這兩個(gè)方法來喚醒它然后進(jìn)入鎖池狀態(tài)。進(jìn)入鎖池狀態(tài)以后繼續(xù)參與鎖的競爭。
當(dāng)一個(gè)處于運(yùn)行狀態(tài)的線程調(diào)用了suspend()方法以后,它就會(huì)進(jìn)入掛起狀態(tài)(這一方法已經(jīng)過時(shí)不建議使用)。掛起狀態(tài)的線程也沒有釋放對象鎖,它需要調(diào)用resume()方法以后才能恢復(fù)到可運(yùn)行狀態(tài)。將線程掛起容易導(dǎo)致程序死鎖。
6 線程的優(yōu)先級
每個(gè)java線程都有一個(gè)優(yōu)先級,優(yōu)先級為1--10,默認(rèn)為5。優(yōu)先級越高只能保證這個(gè)線程執(zhí)行的概率越大,但不能百分之百保證他就一定執(zhí)行。
7 線程死鎖
線程死鎖
死鎖:如果一個(gè)線程集合里面的每個(gè)線程都在等待這個(gè)集合中的其他一個(gè)線程(包括自身)才能繼續(xù)往下執(zhí)行,若無外力他們將無法推進(jìn),這種情況就是死鎖,處于死鎖狀態(tài)的線程稱為死鎖進(jìn)程
死鎖的四個(gè)必要條件:
1)互斥條件:指進(jìn)程對所分配到的資源進(jìn)行排它性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)進(jìn)程占用。如果此時(shí)還有其它進(jìn)程請求資源,則請求者只能等待,直至占有資源的進(jìn)程用畢釋放。
2)請求和保持條件:指進(jìn)程已經(jīng)保持至少一個(gè)資源,但又提出了新的資源請求,而該資源已被其它進(jìn)程占有,此時(shí)請求進(jìn)程阻塞,但又對自己已獲得的其它資源保持不放。
3)不剝奪條件:指進(jìn)程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時(shí)由自己釋放。
4)環(huán)路等待條件:指在發(fā)生死鎖時(shí),必然存在一個(gè)進(jìn)程——資源的環(huán)形鏈,即進(jìn)程集合{P0,P1,P2,···,Pn}中的P0正在等待一個(gè)P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。
二 線程使用和常用類
1 線程的創(chuàng)建方式
方式1:
package com.zyb.test;
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("調(diào)用自定義線程run方法");
super.run();
}
}
package com.zyb.test;
public class TestThread {
public static void main(String[] args) {
//創(chuàng)建線程自定義的對象
MyThread myThread = new MyThread();
//開啟線程,由虛擬機(jī)執(zhí)行該線程對象的run方法。
myThread.start();
}
}
方式2:
package com.zyb.test;
public class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("調(diào)用該線程的run方法");
}
}
package com.zyb.test;
public class TestThread {
public static void main(String[] args) {
//使用runnable的實(shí)現(xiàn)類創(chuàng)建一個(gè)線程對象
Thread thread = new Thread(new MyThread1());
//開啟線程,由虛擬機(jī)執(zhí)行該線程對象的run方法。
thread.start();
}
}
2 Thread
1)構(gòu)造方法
public Thread(Runnable target):使用Runnable的子類對象創(chuàng)建一個(gè)線程對象。
public Thread(Runnable target, String name):使用runnable封裝一個(gè)線程,并給該線程命名為name;
public Thread(ThreadGroup group, Runnable target, String name):使用runnable封裝一個(gè)線程,并給該線程命名為name,并將創(chuàng)建的線程放入線程組group中。
2)靜態(tài)方法
public static native Thread currentThread():獲取當(dāng)前線程對象。
public static native void yield():當(dāng)前線程轉(zhuǎn)讓出cpu的控制權(quán)(切換)。該方法與sleep有點(diǎn)相似,也不會(huì)釋放鎖,只是將當(dāng)前線程放棄時(shí)間片,重新變就緒狀態(tài)。
public static native void sleep(long millis) throws InterruptedException:將線程暫停一段時(shí)間。一定是有當(dāng)前線程調(diào)用,當(dāng)線程調(diào)用后,線程會(huì)放棄CPU執(zhí)行權(quán),但不會(huì)釋放鎖,當(dāng)在指定時(shí)間過后,又變回就緒狀態(tài),去爭取時(shí)間片去執(zhí)行。
3)實(shí)例方法
public final void setPriority(int newPriority):設(shè)置當(dāng)前線程的優(yōu)先級。優(yōu)先級越高越有機(jī)會(huì)調(diào)用。
public final int getPriority():獲取線程的優(yōu)先級。
public final synchronized void setName(String name):設(shè)置線程的名字。
public final String getName():獲取線程的名字。
public long getId():獲取線程的id
public final void setDaemon(boolean on):設(shè)置該線程是否是守護(hù)線程。
public void interrupt():中斷線程。
public final void join() throws InterruptedException:在一個(gè)線程內(nèi)讓另一個(gè)線程對象調(diào)用該方法,必須讓那個(gè)調(diào)用join方法的線程完了過后再執(zhí)行該線程。
注意:該方法在那個(gè)線程里面調(diào)用,就會(huì)阻塞該線程,必須在調(diào)用該方法的線程結(jié)束過后才會(huì)繼續(xù)調(diào)用阻塞線程。
例如:在線程A中先開啟線程B,C,開啟過后B調(diào)用join方法,這時(shí)線程A進(jìn)入阻塞狀態(tài),然后線程B,和線程C,并發(fā)執(zhí)行,知道B執(zhí)行完后,線程A才繼續(xù)執(zhí)行。
public final boolean isDaemon():判斷是否是守護(hù)線程。
public final ThreadGroup getThreadGroup():獲取該線程的線程數(shù)組對象。
什么是守護(hù)線程:
線程分為用戶線程和守護(hù)線程,創(chuàng)建的線程一般默認(rèn)就是用戶線程,如果該線程調(diào)用了setDaemon(true)方法時(shí),就會(huì)變?yōu)槭刈o(hù)線程。
守護(hù)線程就是為了維護(hù)其他線程而存在的,當(dāng)jvm中只有守護(hù)線程而沒有用戶線程時(shí),守護(hù)線程就失去了作用,守護(hù)線程就會(huì)結(jié)束。如果創(chuàng)建的線程對象沒有顯示的調(diào)用setDaemon方法,默認(rèn)就是一個(gè)用戶線程。
守護(hù)線程實(shí)例:
垃圾回收線程就是一個(gè)經(jīng)典的守護(hù)線程,當(dāng)我們的程序中不再有任何運(yùn)行的Thread,程序就不會(huì)再產(chǎn)生垃圾,垃圾回收器也就無事可做,所以當(dāng)垃圾回收線程是JVM上僅剩的線程時(shí),垃圾回收線程會(huì)自動(dòng)離開。它始終在低級別的狀態(tài)中運(yùn)行,用于實(shí)時(shí)監(jiān)控和管理系統(tǒng)中的可回收資源。
守護(hù)線程的生命周期:
守護(hù)進(jìn)程(Daemon)是運(yùn)行在后臺(tái)的一種特殊進(jìn)程。它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。也就是說守護(hù)線程不依賴于終端,但是依賴于系統(tǒng),與系統(tǒng)“同生共死”。那Java的守護(hù)線程是什么樣子的呢。當(dāng)JVM中所有的線程都是守護(hù)線程的時(shí)候,JVM就可以退出了;如果還有一個(gè)或以上的非守護(hù)線程則JVM不會(huì)退出。
3 ThreadLocal
關(guān)于ThreadLocal的概念很多,這里只說本人在使用中對其的理解;首先我們需要知道,Thread有個(gè)threadLocals 屬性,如下
ThreadLocal.ThreadLocalMap threadLocals = null;
該屬性是每個(gè)線程都有的屬性(雖然該屬性不是沒有實(shí)現(xiàn)Map接口,但是可以將其當(dāng)成一個(gè)Map來理解),可以往其中放值,放入其中的值,只要線程沒有結(jié)束,該線程就能夠取出來使用,其他線程是無法調(diào)用的。個(gè)人理解ThreadLocal就是一個(gè)操作線程ThreadLocalMap屬性的工具和鑰匙,說它是工具是因?yàn)樗哂胁僮鳟?dāng)前線程ThreadLocalMap的能力,能往其中存取值。說它是鑰匙是因?yàn)樵谕鵗hreadLocalMap中添加值時(shí),將ThreadLocal本身作為key傳入進(jìn)去,意思也就是說,使用那個(gè)ThreadLocal放入的值,也只能通過該ThreadLocal獲取,其他的ThreadLocal是不能獲取的。由于是用ThreadLocal作為的key,所以一個(gè)ThreadLocal只能往線程中存取一個(gè)值。如果沒有理解,請看下面方法源碼。
ThreadLocal的主要方法:
1)public T get() :
源碼如下:
public T get() {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取當(dāng)前線程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//以該ThreadLocal作為key獲取值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//沒有map與值的話返回null
return setInitialValue();
}
2) public void set(T value):
public void set(T value) {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取當(dāng)前線程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//如果有map,將當(dāng)前ThreadLocal作為key將值放入進(jìn)去
map.set(this, value);
else
//如果map不存在,創(chuàng)建map并將當(dāng)前ThreadLocal作為key將值放入進(jìn)去
createMap(t, value);
}
3)protected T initialValue()
protected T initialValue() {
return null;
}
當(dāng)調(diào)用get方法時(shí),如果沒有值,默認(rèn)返回null,如果不想返回空,需要自己手動(dòng)重寫該方法。
4) public void remove():
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//將該ThreadLocal存入的值從當(dāng)前線程的ThreadLocalMap里面移除。
m.remove(this);
}
由于ThreadLocalMap是與線程綁定的,所以里面存儲(chǔ)的值是與線程的生命周期保持一致的,比如如果使用了線程池,將線程進(jìn)行復(fù)用的話,不使用remove對其進(jìn)行刪除的話,存儲(chǔ)的值的生命周期將和線程同步。
4 Synchronized
使用原理
synchronized是一種同步鎖,它的修飾對象有以下幾種。
1)、修飾一個(gè)代碼塊:當(dāng)修飾一個(gè)代碼塊時(shí),他會(huì)將該對象的該段代碼塊進(jìn)行上鎖,當(dāng)一個(gè)線程在執(zhí)行一個(gè)對象的該代碼塊,那么另一個(gè)線程就不能執(zhí)行這個(gè)對象的該代碼塊(注意是同一個(gè)對象的代碼塊),必須等前一個(gè)線程執(zhí)行完后,后一個(gè)線程才可以去執(zhí)行。
注意:當(dāng)修飾代碼塊時(shí),可以指定修飾那個(gè)對象的代碼塊。
注意:synchronized (Object u) {}當(dāng)u是對象時(shí),表示只有在獲取了對象u的鎖以后才能執(zhí)行{}里面的方法。當(dāng)u是一個(gè)類對象時(shí),表示只有在獲取了該類的鎖以后才能執(zhí)行{}里面的方法。關(guān)于對象鎖與類鎖,請看3
2)、修飾一個(gè)方法:當(dāng)修飾一個(gè)方法時(shí),他會(huì)將該對象的該方法進(jìn)行上鎖,如果兩個(gè)線程都要調(diào)用該對象的該方法就不會(huì)同時(shí)訪問,只能等一個(gè)線程訪問過后,另一個(gè)線程才能執(zhí)行。
注意:一個(gè)對象只有一個(gè)鎖,當(dāng)一個(gè)線程訪問該對象的上鎖的代碼A或者方法A時(shí),他會(huì)先獲取該對象的鎖,只有獲取到過后才會(huì)執(zhí)行上鎖代碼,如果另一個(gè)線程要訪問該對象的上鎖代碼B或者方法B時(shí),如果該對象的鎖被其他線程獲取,那么就不能執(zhí)行上鎖代碼B或者方法B??傊?,如果一個(gè)對象有兩個(gè)方法被上鎖,但對象只有一個(gè)鎖,所以這兩個(gè)方法不能同時(shí)在兩個(gè)線程中調(diào)用,只有等一個(gè)線程調(diào)用完以后釋放對象鎖,另一個(gè)線程獲取對象鎖后才能執(zhí)行另一個(gè)方法。
注意:synchronized可以被繼承,只是如果你要覆蓋該方法的話,必須將該方法顯示的寫出synchronized,才有上鎖的作用。
**3)
、修飾一個(gè)靜態(tài)的方法:**當(dāng)修飾一個(gè)靜態(tài)方法時(shí),他會(huì)將該類的該靜態(tài)方法進(jìn)行上鎖,如果兩個(gè)線程都要調(diào)用該類的該靜態(tài)方法就不會(huì)同時(shí)訪問,只能等一個(gè)線程訪問過后,另一個(gè)線程才能執(zhí)行。
注意:如果是靜態(tài)方法上加鎖,它作用于所有的對象,當(dāng)在一個(gè)線程中通過對象調(diào)用該靜態(tài)方法或通過類名調(diào)用,那么另一線程就不能調(diào)用該類的另一個(gè)靜態(tài)方法(不管是通過類名調(diào)用還是通過對象調(diào)用),但對上鎖的非靜態(tài)方法沒有影響,在另一個(gè)線程中任然能執(zhí)行上鎖的非靜態(tài)方法,不管其他線程是否使用上鎖的靜態(tài)方法。
我的理解就是每一個(gè)對象有一個(gè)鎖,每一個(gè)類也有一個(gè)鎖,當(dāng)一個(gè)線程要通過對象訪問類的上鎖非靜態(tài)方法時(shí),就要獲取該對象的鎖才能訪問,如果鎖被其他線程獲取,這個(gè)線程只能等待其他線程釋放對象鎖。當(dāng)一個(gè)線程通過對象或者類訪問類的上鎖靜態(tài)方法時(shí),就要獲取該類的鎖才能訪問。
注意對象鎖和類鎖是兩個(gè)不同的鎖,就是當(dāng)類鎖被其他線程獲取過后,當(dāng)前線程任然可以獲取對象鎖調(diào)用上鎖的非靜態(tài)方法。
以上都是對上鎖的方法進(jìn)行的討論,如果方法沒有上鎖,那么任意的線程都可以訪問該方法,不用獲取對象或者類的鎖。
心得:當(dāng)要進(jìn)入synchronized的代碼塊時(shí),必須先獲取()內(nèi)對象或者類的鎖,當(dāng)執(zhí)行完后立刻釋放()內(nèi)對象或者類的鎖。注意,只有在執(zhí)行到synchronized代碼塊時(shí)才獲取對象鎖,執(zhí)行完后立刻釋放鎖。對象調(diào)用加鎖的方法也是一樣的原理。另外,在synchronized代碼塊里面也可以繼續(xù)獲取其他對象的鎖。
總結(jié):
A. 無論synchronized關(guān)鍵字加在方法上還是對象上,如果它作用的對象是非靜態(tài)的,則它取得的鎖是對象;如果synchronized作用的對象是一個(gè)靜態(tài)方法或一個(gè)類,則它取得的鎖是對類,該類所有的對象同一把鎖。
B. 每個(gè)對象只有一個(gè)鎖(lock)與之相關(guān)聯(lián),誰拿到這個(gè)鎖誰就可以運(yùn)行它所控制的那段代碼。
C. 實(shí)現(xiàn)同步是要很大的系統(tǒng)開銷作為代價(jià)的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
線程相關(guān)的方法Wait/notify/notifyAll(都是Object的方法)
public final void wait() throws InterruptedException:該方法只能在synchronized代碼快里面調(diào)用,線程會(huì)終止并釋放調(diào)用該方法的對象的鎖,進(jìn)入等待狀態(tài),只有調(diào)用該方法的對象調(diào)用notify方法過后被喚醒,進(jìn)入就緒狀態(tài)。
public final native void wait(long timeout) throws InterruptedException:該方法和wait()很相似,只是他進(jìn)入等待狀態(tài)過后,有個(gè)最大等待時(shí)間,如果沒有超過最大等待時(shí)間,那么和wait()一模一樣,但是如果超過等待時(shí)間,他不用notify喚醒,也會(huì)自動(dòng)醒過來。
public final native void notify():喚醒由對象調(diào)用wait()方法等待的線程中的任意一個(gè)線程。
就是一個(gè)對象在很多線程中調(diào)用wait()方法,該對象調(diào)用notify方法就是喚醒他們中的某一個(gè)線程。
public final native void notifyAll():喚醒所有的處于等待狀態(tài)的線程。