一. 概述
使用的線程的目的有如下幾點:
- 異步锨匆。所謂異步崭别,字義上來講就是同時做多個不同的事。
例如恐锣,你正在和戀人聊QQ茅主,而此時你正在發(fā)送一個文件,如果收發(fā)消息和上傳文件在同一個線程土榴,那么當(dāng)你發(fā)送文件開始诀姚,你便需要等待文件上傳完成后,才能發(fā)送下一條消息玷禽,如果再加上文件太大赫段、網(wǎng)速很差等等因素,就可能導(dǎo)致你和你戀人在幾個小時內(nèi)只說了一句話矢赁,那你們之間可能就GG了糯笙。
- 并發(fā)。所謂并發(fā)撩银,字義上來講就是同時做多個相同的事给涕。
例如,雙11上淘寶買東西额获,在同一時間內(nèi)够庙,阿里的服務(wù)器收到一多個購買的請求,如果處理這些請求的方式是處理完一條再處理下一條的話抄邀,那么你買個衣服坑你就要等到地老天荒了耘眨。
雖然我在這里將線程的目的分成了兩種,但它們的本質(zhì)是一樣:在同一時間做多個不同的事境肾。值得注意的是剔难,如果你電腦的CPU是單核單線程的話胆屿,這個“同一時間”是有事件差的,可能CPU這一毫秒在執(zhí)行線程A钥飞,下一毫秒在執(zhí)行線程B,但完全同一時間同時執(zhí)行線程A和線程B是不可能的衫嵌。
二. 如何創(chuàng)建線程
實現(xiàn)線程有兩種方式读宙,涉及到的類有兩個,分別是:java.lang.Thread和java.lang.Runnable楔绞。創(chuàng)建并啟動線程的方式如下:
Thread thread = new Thread();//創(chuàng)建線程實例
thread.start();//啟動線程
但是這僅僅是創(chuàng)建了一條新的線程并啟動了它结闸,該線程并不會執(zhí)行任何邏輯,那么我們怎么讓它執(zhí)行相關(guān)邏輯呢酒朵?
方式1:繼承Thread類桦锄,重寫run方法。
public class DemoThread extends Thread {
public void run() {
//TODO 線程中需要執(zhí)行的相關(guān)邏輯
}
public static void main(String[] args) {
Thread thread = new DemoThread();//創(chuàng)建線程實例
thread.start();//啟動線程
}
}
方式二:實現(xiàn)Runnable接口呆万,并在Thread構(gòu)造方法中傳入實現(xiàn)的Runnable實例埋泵。
public class DemoRunnable implements Runnable {
public void run() {
//TODO 線程中需要執(zhí)行的相關(guān)邏輯
}
public static void main(String[] args) {
Runnable runnable = new DemoRunnable();//創(chuàng)建一個實現(xiàn)了Runnable接口的類的實例
Thread thread = new Thread(runnable);//創(chuàng)建線程實例润绎,并在構(gòu)造方法中傳入實現(xiàn)了Runnable類的實例
thread.start();//啟動線程
}
}
注: 這兩種方法都能讓線程執(zhí)行我們需要執(zhí)行的邏輯代碼。當(dāng)你調(diào)用start()函數(shù)啟動線程后图甜,程序會在該線程中調(diào)用Thread類自己的run()方法,如果你在創(chuàng)建線程時傳入了Runnable的實例鳖眼,那么在Thread類的run()方法中黑毅,會調(diào)用Runnable的run()方法。
特別需要注意的是钦讳,new Thread()只是創(chuàng)建了Thread類的一個實例矿瘦,此時并沒有創(chuàng)建出一條線程。而啟動線程是調(diào)用start()方法而不是run()方法:直接調(diào)用run()方法時你只是調(diào)用的一個普通方法去執(zhí)行相關(guān)邏輯代碼愿卒,邏輯依然執(zhí)行再你調(diào)用run()方法的線程中缚去;而調(diào)用start()方法,jvm才會創(chuàng)建一條新線程琼开,而此時run()才會在該線程中自動被回調(diào)執(zhí)行病游。
三、線程同步
什么是同步稠通?
同步是某個任務(wù)在同一時間只能由一個線程在執(zhí)行衬衬,等這個線程執(zhí)行完成后,下一個線程才能執(zhí)行改橘。那么既然線程的目的是為了異步滋尉,那么又為什么需要同步呢?這主要是由于數(shù)據(jù)安全造成的飞主。多個線程在同時操作一個數(shù)據(jù)狮惜,當(dāng)線程A還沒沒來得急使用該數(shù)據(jù)時高诺,線程B就改了該數(shù)據(jù)的狀態(tài)或值,這是就可能導(dǎo)致結(jié)果的錯誤碾篡,甚至是程序運行的異常虱而。就像由此你和朋友都很餓,然后看到一個蘋果开泽,你正準(zhǔn)備吃牡拇,然后你朋友直接給你搶來吃的還剩核,然后......例子可能不精確穆律,見諒惠呼。
實現(xiàn)線程同步主要要使用兩個點:鎖和synchronized關(guān)鍵字。
1.鎖峦耘。
鎖是java中一種機制剔蹋,分為對象鎖和類鎖。每個對象/類都有一個單一的鎖(需要注意的是辅髓,一個類可以有多個對象泣崩,每個對象的鎖也都是獨立且單一的,互不干擾)洛口。當(dāng)一個線程獲取到某個對象/類的鎖后律想,除非該鎖被釋放,否則其他線程是不能獲取到該對象/類的鎖绍弟,而此時如果其他線程要獲取該對象的鎖技即,就只能等待。而上面所說的某個任務(wù)樟遣,便是獲取到同一個鎖的代碼塊而叼,他可能里面的邏輯并不相同,但是獲取的鎖是相同的豹悬。
2葵陵、synchronized關(guān)鍵字用于需要同步的邏輯中,實現(xiàn)方式分為:同步方法和同步塊瞻佛。synchronized的目的就是獲取某個對象/類的鎖脱篙。分為:
同步塊。其中的object參數(shù)便是你要獲取的鎖的對象伤柄。
synchronized (object) {
//TODO 這里是同步塊中需要執(zhí)行的邏輯
}
對象同步方法绊困。該synchronized獲取到的鎖是類A時候化后的對象的鎖。
public class A {
public synchronized void syncMethod() {
//TODO 這里是同步方法中需要執(zhí)行的邏輯
}
}
類同步方法适刀,即靜態(tài)同步方法秤朗。該synchronized獲取到的鎖是類A的鎖。
public class A {
public static synchronized void staicSyncMethod() {
}
}
同步的例子
public class ThreadSyncDemo {
private static Object locker = new Object();//需要獲取鎖的對象
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (locker) {
for (int i = 0; i < 1000; ++i) {
System.out.println(i);
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (locker) {
for (int i = 0; i < 1000; ++i) {
System.out.println("aaa");
}
}
}
});
t1.start();
t2.start();
}
}
執(zhí)行這段代碼時笔喉,你先將synchronized塊去掉取视,保留里面的邏輯硝皂,然后執(zhí)行,此時你會返現(xiàn)控制臺輸出中作谭,數(shù)字和字母是交叉出現(xiàn)的稽物,說明是異步執(zhí)行的。然后加上同步快折欠,再執(zhí)行贝或,你會發(fā)現(xiàn)此時數(shù)字先打印完后才打印的字母(或者字母先打印完后才打印的字母),說明是同步執(zhí)行的怨酝。對象同步方法和靜態(tài)同步方法原理的列子這里就不再舉出傀缩,只要知道到底是獲取到那個對象或類的鎖那先,其他都是類似的农猬。
死鎖
死鎖,顧名思義就是鎖死了售淡,執(zhí)行不下去了斤葱。若有兩個線程A和B,若線程A在執(zhí)行時獲取到對象X的鎖揖闸,在同步塊中又獲取對象Y的鎖揍堕,而此時線程B在執(zhí)行時獲取到對象Y的鎖,而B的同步塊中又在獲取X的鎖汤纸。再某種比較的極端情況下衩茸,A持有X的鎖,B持有Y的鎖贮泞,A執(zhí)行到獲取Y的鎖時B未執(zhí)行完楞慈,A阻塞等待,然后B又獲取X的鎖啃擦,而此時A還在阻塞等待B持有的Y的鎖囊蓝,未釋放X的鎖,導(dǎo)致B也阻塞等待令蛉。A和B都在阻塞等待聚霜,然后就沒有然后了~~~~
所以避免死鎖的其中之一便是盡量不要交叉獲取鎖。當(dāng)然這不是唯一導(dǎo)致死鎖的可能珠叔。
下面是一個死鎖的列子:
public class ThreadSyncDemo {
private static Object locker = new Object();
public static void main(String[] args) {
final Thread t1 = new Thread() {
@Override
@Deprecated
public void run() {
synchronized (locker) {//獲取ThreadSyncDemo的類鎖
for (int i = 0; i < 10000; ++i) {
System.out.println(i);
if (i == 1000) {
this.suspend();//掛起該線程蝎宇,此方法暫停線程執(zhí)行,但不會釋放鎖
}
}
}
}
};
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (locker) {//獲取ThreadSyncDemo的類鎖
for (int i = 0; i < 10000; ++i) {
System.out.println("aaa");
}
}
}
});
t1.start();
try{
Thread.sleep(50)//暫停下祷安,保證他t1先執(zhí)行夫啊。
}catch(Exception e){}
t2.start();
}
}
上面例子中,線程t1和線程t2的同步塊都是獲取的ThreadSyncDemo.class的鎖辆憔,在t1同步塊中撇眯,當(dāng)i=1000時报嵌,暫停了t1線程的執(zhí)行,此時t1的同步塊未釋放鎖熊榛,而t2一直在等待t1釋放鎖锚国,如果t1線程不繼續(xù)執(zhí)行,則t2也執(zhí)行不了玄坦。
四. Thread類的相關(guān)方法
wait()與notify()/notifyAll()方法
實際上血筑,wait()、notify()煎楣、notifyAll()這三個方法并不是Thread類的專有方法豺总,而是Object的方法,也就是說择懂,每個對象都存在這三個方法喻喳。
需要注意的是,這三個方法都只能在同步塊/同步方法中執(zhí)行困曙,其他地方執(zhí)行時沒有意義的表伦。而且需要同步塊中獲取到的鎖的對象來調(diào)用才有效果。
wait()顧名思義讓同步塊暫停執(zhí)行并等待慷丽,此時該同步塊會讓出獲取到的鎖蹦哼,讓其他線程執(zhí)行獲取同一把鎖的同步塊。而在其他線程執(zhí)行完后要糊,調(diào)用notify()/notifyAll()方法纲熏,之前等待的的同步塊就會繼續(xù)執(zhí)行。但是锄俄,如果調(diào)用了notify()/notifyAll()之后局劲,后面有長時間任務(wù)二導(dǎo)致鎖未被釋放,等待中的同步塊也需要等鎖被釋放后才會繼續(xù)往下執(zhí)行珊膜。如果同一個鎖有多個地方等待容握,就需要使用notifyAll()來全部喚醒,使他們重新爭奪鎖的行列中车柠,誰先獲取到鎖就誰先執(zhí)行剔氏。注意如果等待的是多個,nofity()和nofityAll()后最先獲取的鎖的是那個竹祷,由jvm決定谈跛。
wait()方法有兩個個重載方法,wait(long timeout)和wait(long timeout, int nanos),其中timeout是等待時間塑陵,如果timeout=0感憾,則表示一直等待知道nofity()/nofityAll()被調(diào)用切獲取到鎖后繼續(xù)執(zhí)行,timeout>0則表示等待多少時間后令花,只要獲取到鎖就繼續(xù)執(zhí)行阻桅。至于nanos凉倚,表示納秒,值在0-999999之間嫂沉,為了更好的控制時間稽寒。
public class ThreadSyncDemo {
private static Object locker_1 = new Object();
public static void main(String[] args) {
final Thread t1 = new Thread() {
@Override
@Deprecated
public void run() {
synchronized (locker_1) {
for (int i = 0; i < 1000; ++i) {
System.out.println(i);
if (i == 100) {
try {
locker_1.wait();//當(dāng)i=100是,執(zhí)行wait()方法趟章,釋放鎖杏糙,進入等待狀態(tài)
} catch (Exception e) {
}
}
}
}
}
};
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (locker_1) {
for (int i = 0; i < 100; ++i) {
System.out.println("aaa");
}
locker_1.notify();//當(dāng)執(zhí)行完后,通知等待的線程繼續(xù)執(zhí)行蚓土。
}
}
});
t1.start();
try{
Thread.sleep(50) //保證t1先執(zhí)行
}catch(Exception e)
t2.start();
}
}
上例宏侍,控制臺先會打印0-100,當(dāng)線程t1中i=100時蜀漆,調(diào)用locker_1.wait()使其釋放鎖并進入等待狀態(tài)谅河,此時線程t2獲取到鎖,控制臺打印100個aaa,然后調(diào)用locker_1.notify()后嗜愈,線程t1會繼續(xù)在控制臺打印101-999旧蛾。
interrupt(),interrupted(),isInterrupt()方法
interrupt()方法看起來是中斷線程莽龟,但實際上蠕嫁,當(dāng)你調(diào)用interrupt()方法后,你發(fā)現(xiàn)線程該干嘛還是再干嘛毯盈,除非你的線程中存再調(diào)用sleep()剃毒、wait()等方法的時候,此時會拋出InterruptedException異常搂赋,以供手動處理線程停止赘阀。isInterrupt()返回線程是否是中斷狀態(tài)。interrupted()方法是類方法脑奠。從Thread類的源碼看基公,isInterrupt()和interrupted()都會調(diào)用以下方法:
private native boolean isInterrupted(boolean ClearInterrupted);
不同是isInterrupt()傳入的ClearInterrupted=false,
interrupted()方法傳入的ClearInterrupted=true,當(dāng)ClearInterrupted=true時宋欺,線程的中斷狀態(tài)會被清除轰豆,也就是說此時isInterrupt()方法的返回值是false。
suspend()與resume()方法
已廢棄的方法齿诞,用于暫停和重新開始執(zhí)行線程酸休。和wait()不同的是,suspend()是通過線程的實例調(diào)用的祷杈,而不是鎖對象斑司,調(diào)用也不需要在同步塊中調(diào)用,而且suspend()方法調(diào)用后并不會釋放鎖但汞。列子:
public class ThreaSuspendResumeDemo {
public static void main(String[] args) {
final Timer timer = new Timer();
final Thread t = new Thread() {
@Deprecated
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
if (i == 1000) {
this.suspend();//暫停線程
}
}
timer.cancel();
}
};
t.start();
timer.schedule(new TimerTask() {
@Override
@Deprecated
public void run() {
t.resume();//3秒后將線程喚醒
}
}, 3000);
}
}
stop()方法
廢棄的方法宿刮。暴力終止線程互站,調(diào)用此方法后,線程中未執(zhí)行的語句將不會再執(zhí)行僵缺。但是isAlive()方法和isInterrupt方法的返回值依然是false云茸,所以,暴力如此谤饭,想想都可怕标捺,謹(jǐn)慎使用。
public class ThreadStopDemo {
public static void main(String[] args) {
final Thread t = new Thread() {
@Deprecated
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
if (i == 1000) {
this.stop();//i=1000時就停止了揉抵,控制臺只會打印0-1000亡容。
}
}
System.out.println("Is this thread alive?" + this.isAlive());//線程已經(jīng)終止,這條語句是不會執(zhí)行的
}
};
t.start();
final Timer timer = new Timer();
timer.schedule(new TimerTask(){
@Override
@Deprecated
public void run() {
System.out.println("Is this thread alive?" + t.isAlive());//false
System.out.println("Is this thread interrupt?" + t.interrupted());//false
}
}, 3000);
}
}
destroy()方法
額冤今,為什么要講這個方法呢闺兢?因為我以為會和stop()方法一樣的喪心病狂,但是我錯了戏罢。從Thread.destroy()方法的源碼來看屋谭,結(jié)果讓人發(fā)呆流鼻涕。源碼如下:
/**
* Throws {@link NoSuchMethodError}.
*
* @deprecated This method was originally designed to destroy this
* thread without any cleanup. Any monitors it held would have
* remained locked. However, the method was never implemented.
* If if were to be implemented, it would be deadlock-prone in
* much the manner of {@link #suspend}. If the target thread held
* a lock protecting a critical system resource when it was
* destroyed, no thread could ever access this resource again.
* If another thread ever attempted to lock this resource, deadlock
* would result. Such deadlocks typically manifest themselves as
* "frozen" processes. For more information, see
* <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">
* Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
* @throws NoSuchMethodError always
*/
@Deprecated
public void destroy() {
throw new NoSuchMethodError();
}
是不是亮瞎了鈦合金狗眼!!!!!
五.守護線程
線程分為用戶(User)線程和守護(Daemon)線程龟糕,守護(Daemon)線程的實現(xiàn)就是線程在調(diào)用start()方法前桐磁,先調(diào)用setDaemon(true)方法。區(qū)別是讲岁,普通用戶(User)線程我擂,只要線程還在執(zhí)行,那么程序就永遠(yuǎn)不會退出缓艳;而守護(Daemon)線程只要程序主線程執(zhí)行完后校摩,守護(Daemon)線程也就被終止。守護(Daemon)線程常作為輔佐的的作用阶淘。
以上便是此次線程學(xué)習(xí)的第一部分總結(jié)衙吩,如有意見或建議歡迎提出,相互探討才能共同成長與進步溪窒。后面講繼續(xù)研究線程池和線程調(diào)度坤塞。