摘要:當多個線程訪問實例時,每個線程維護提供的獨立的變量副本。而則從另一個角度來解決多線程的并發(fā)訪問。在執(zhí)行同步代碼塊的過程中,遇到異常而導致線程終止。在執(zhí)行同步代碼塊的過程中,其他線程執(zhí)行了當前對象的方法,當前線程被暫停,但不會釋放鎖。
一、Thread.start()與Thread.run()的區(qū)別
通過調(diào)用Thread類的start()方法來啟動一個線程,這時此線程是處于就緒狀態(tài),并沒有運行。然后通過此Thread類調(diào)用方法run()來完成其運行操作的,這里方法run()稱為線程體,它包含了要執(zhí)行的這個線程的內(nèi)容,Run方法運行結束,此線程終止,而CPU再運行其它線程。
而如果直接用Run方法,這只是調(diào)用一個方法而已,程序中依然只有“主線程”這一個線程,并沒有開辟新線程,其程序執(zhí)行路徑還是只有一條,這樣就沒有達到寫線程的目的。
測試代碼如下
代碼如下:
public class MyThread implements Runnable {
public void run() {
System.err.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
Thread t1 = new Thread(thread, "Thread-1");
Thread t2 = new Thread(thread, "Thread-2");
t1.run();
t2.run();
}
}
輸出結果為
當前進程為:main
當前進程為:main
改為用start方法:
代碼如下:
package thread;
public class MyThread implements Runnable {
public void run() {
System.err.println(">>當前進程為:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
Thread t1 = new Thread(thread, "Thread-1");
Thread t2 = new Thread(thread, "Thread-2");
t1.start();
t2.start();
}
}
結果為:
當前進程為:Thread-1
當前進程為:Thread-2
二、ThreadLocal類詳解
ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。其實,ThreadLocal并不是一個Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
下面是線程局部變量(ThreadLocal variables)的關鍵點:
一個線程局部變量(ThreadLocal variables)為每個線程方便地提供了一個多帶帶的變量。在多個線程操作該變量時候能夠互不影響,因為每個線程操作的實際上是改變量的副本。ThreadLocal實例通常作為靜態(tài)的私有的(private static)字段出現(xiàn)在一個類中,這個類用來關聯(lián)線程。當多個線程訪問ThreadLocal實例時,每個線程維護ThreadLocal提供的獨立的變量副本。
下面是測試代碼,用于測試:作用于一個對象上面的三個線程來操作同一個ThreadLoacl對象(integer 類型),看是否會出現(xiàn)臟讀等現(xiàn)象:
代碼如下:
public class Test implements Runnable {
private static ThreadLocal
public void run() {
num.set(0);
for (int i = 0; i < 3; i++) {
num.set(num.get() + 1);
System.out.println(Thread.currentThread().getName() + ":num="
num.get());
}
}
public static void main(String[] args) {
Test test = new Test();
Thread t1 = new Thread(test, "Thread-1");
Thread t2 = new Thread(test, "Thread-2");
Thread t3 = new Thread(test, "Thread-3");
t1.start();
t2.start();
t3.start();
}
}
運行結果如下:
Thread-3:num=1
Thread-2:num=1
Thread-1:num=1
Thread-2:num=2
Thread-3:num=2
Thread-2:num=3
Thread-1:num=2
Thread-1:num=3
Thread-3:num=3
從上面可以看出,完全沒有出現(xiàn)臟讀等的現(xiàn)象,因此ThreadLocal線程安全。
常用的使用:當DAO類作為一個單例類時,數(shù)據(jù)庫鏈接(connection)被每一個線程獨立的維護,互不影響。
可以用來控制session的創(chuàng)建和使用,如下ThreadLocal
ThreadLoacal與同步機制的比較:
1、在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候?qū)ψ兞窟M行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
2、而ThreadLocal則從另一個角度來解決多線程的并發(fā)訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。
3、概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
三、InvalidMonitorStateException異常
調(diào)用wait()/notify()/notifyAll()中的任何一個方法時,如果當前線程沒有獲得該對象的鎖,那么就會拋出IllegalMonitorStateException的異常(也就是說程序在沒有執(zhí)行對象的任何同步塊或者同步方法時,仍然嘗試調(diào)用wait()/notify()/notifyAll()時)。由于該異常是RuntimeExcpetion的子類,所以該異常不一定要捕獲(盡管你可以捕獲只要你愿意).作為RuntimeException,此類異常不會在wait(),notify(),notifyAll()的方法簽名提及。
如下代碼,劃線的部分會發(fā)生該異常,因為沒有對該對象執(zhí)行同步操作。
代碼如下:
public class Common implements Runnable { public synchronized void method1() throws InterruptedException { Thread.sleep(1000); System.out.println("Method 1 called"); Thread.sleep(1000); System.out.println("Method 1 done"); } public synchronized void method2() throws InterruptedException { Thread.sleep(1000); System.err.println("Method 2 called"); Thread.sleep(1000); System.err.println("Method 2 done"); } public void run() { System.out.println("Running " + Thread.currentThread().getName()); try { if (Thread.currentThread().getName().equals("Thread-1")) { this.wait(1000); method1(); } else { method2(); notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { Common c = new Common(); Thread t1 = new Thread(c, "Thread-1"); Thread t2 = new Thread(c, "Thread-2"); t1.start(); t2.start(); } }
改為一下代碼,劃線部分:
代碼如下:
public class Common implements Runnable { public synchronized void method1() throws InterruptedException { Thread.sleep(1000); System.out.println("Method 1 called"); Thread.sleep(1000); System.out.println("Method 1 done"); } public synchronized void method2() throws InterruptedException { Thread.sleep(1000); System.err.println("Method 2 called"); Thread.sleep(1000); System.err.println("Method 2 done"); } public void run() { System.out.println("Running " + Thread.currentThread().getName()); try { if (Thread.currentThread().getName().equals("Thread-1")) { synchronized(this){ this.wait(1000); } method1(); } else { method2(); synchronized (this) { notifyAll(); } } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { Common c = new Common(); Thread t1 = new Thread(c, "Thread-1"); Thread t2 = new Thread(c, "Thread-2"); t1.start(); t2.start(); } }
四、sleep()和wait()和suspend()的區(qū)別
區(qū)別一:
sleep是Thread類的方法,是線程用來控制自身流程的,比如有一個要報時的線程,每一秒中打印出一個時間,那么我就需要在print方法前面加上一個sleep讓自己每隔一秒執(zhí)行一次。就像個鬧鐘一樣。 sleep() 指示當前線程暫停執(zhí)行指定時間,把執(zhí)行機會讓給其他線程,但是監(jiān)控狀態(tài)依然保持,到時后會自動恢復。調(diào)用sleep不會釋放對象鎖。
wait是Object類的方法,用來線程間的通信,這個方法會使當前擁有該對象鎖的線程等待,直到其他線程調(diào)用notify方法時再醒來,不過你也可以給它指定一個時間,自動醒來。這個方法主要是用在不同線程之間的調(diào)度。對象調(diào)用wait()方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發(fā)出notify方法(或notifyAll)后本線程才進入對象鎖定池準備獲得對象鎖進入運行狀態(tài)。
區(qū)別二 :
調(diào)用wait方法會釋放當前線程的鎖,其實線程間的通信是靠對象來管理的,所有操作一個對象的線程是這個對象通過自己的wait方法來管理的。就好像這個對象是電視機,三個人是三個線程,那么電視機的遙控器就是這個鎖,假如現(xiàn)在A拿著遙控器,電視機調(diào)用wait方法,那么A就交出自己的遙控器,由jVM虛擬機調(diào)度,遙控器該交給誰。
調(diào)用sleep方法不會釋放鎖,因為sleep()是一個線程用于管理自己的方法,不涉及線程通信。還是上面的例子,如果A拿遙控器的期間,他可以用自己的sleep每隔十分鐘調(diào)一次臺,而在他調(diào)臺休息的十分鐘期間,遙控器還在他的手上,其他人無法獲得遙控器。
suspend() 方法容易發(fā)生死鎖。調(diào)用suspend()的時候,目標線程會停下來,但卻仍然持有在這之前獲得的鎖。此時,其他任何線程都不能訪問鎖定的資源,除非被"掛起"的線程恢復運行。對任何線程來說,如果它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會造成死鎖
在以下情況下,持有鎖的線程會釋放鎖:
執(zhí)行完同步代碼塊。
在執(zhí)行同步代碼塊的過程中,遇到異常而導致線程終止。
在執(zhí)行同步代碼塊的過程中,執(zhí)行了鎖所屬對象的wait()方法,這個線程會釋放鎖,進行對象的等待池。
在以下情況下,線程雖然停止執(zhí)行,但是線程不會釋放鎖:
在執(zhí)行同步代碼塊的過程中,執(zhí)行了Thread.sleep()方法,當前線程放棄CPU,開始睡眠,在睡眠中不會釋放鎖。
在執(zhí)行同步代碼塊的過程中,執(zhí)行了Thread.yield()方法,當前線程放棄CPU,但不會釋放鎖。
在執(zhí)行同步代碼塊的過程中,其他線程執(zhí)行了當前對象的suspend()方法,當前線程被暫停,但不會釋放鎖。
五、在靜態(tài)方法上使用同步
JAVA只識別兩種類型的鎖:對象鎖和類鎖。
同步靜態(tài)方法時會獲取該類的"Class”對象,所以當一個線程進入同步的靜態(tài)方法中時,線程監(jiān)視器獲取類本身的鎖,對整個類加鎖,其它線程不能進入這個類的任何靜態(tài)同步方法。它不像實例方法,因為多個線程可以同時訪問不同實例同步實例方法。測試代碼如下:
代碼如下:
public class Common implements Runnable { public synchronized static void method1() throws InterruptedException { Thread.sleep(1000); System.out.println("Method 1 called"); Thread.sleep(1000); System.out.println("Method 1 done"); } public synchronized static void method2() throws InterruptedException { Thread.sleep(1000); System.err.println("Method 2 called"); Thread.sleep(1000); System.err.println("Method 2 done"); } public void run() { System.out.println("Running " + Thread.currentThread().getName()); try { if (Thread.currentThread().getName().equals("Thread-1")) { method1(); } else { method2(); // Thread.currentThread().notify(); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { //以下代碼創(chuàng)建了不同的對象上的不同線程,用來測試對于同一個類,會不會有鎖 Common c1 = new Common(); Common c2 = new Common(); Thread t1 = new Thread(c1, "Thread-1"); Thread t2 = new Thread(c2, "Thread-2"); t1.start(); t2.start(); } }
執(zhí)行結果如下:
Running Thread-2
Running Thread-1
Method 2 called
Method 2 done
Method 1 called
Method 1 done
六、在一個對象上兩個線程可以在同一時間分別調(diào)用兩個不同的同步實例方法嗎?
不能,因為一個對象已經(jīng)同步了實例方法,線程獲取了對象的對象鎖。所以只有執(zhí)行完該方法釋放對象鎖后才能執(zhí)行其它同步方法。測試代碼如下:
代碼如下:
public class Common implements Runnable { public synchronized void method1() throws InterruptedException { Thread.sleep(1000); System.out.println("Method 1 called"); Thread.sleep(1000); System.out.println("Method 1 done"); } public synchronized void method2() throws InterruptedException { Thread.sleep(1000); System.err.println("Method 2 called"); Thread.sleep(1000); System.err.println("Method 2 done"); } public void run() { System.out.println("Running " + Thread.currentThread().getName()); try { if (Thread.currentThread().getName().equals("Thread-1")) { method1(); } else { method2(); // Thread.currentThread().notify(); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { Common c = new Common(); Thread t1 = new Thread(c, "Thread-1"); Thread t2 = new Thread(c, "Thread-2"); t1.start(); t2.start(); //以下代碼作為對比,創(chuàng)建不同的對象,則就不會受對象鎖的干擾了 //Common c1 = new Common(); //Common c2 = new Common(); //c1.start(); //c2.start(); } }
執(zhí)行結果如下:
Running Thread-1
Running Thread-2
Method 1 called
Method 1 done
Method 2 called
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.hztianpu.com/yun/65453.html
摘要:超詳細的面試題總結一之基本知識多線程和虛擬機創(chuàng)建線程有幾種不同的方式你喜歡哪一種為什么繼承類實現(xiàn)接口應用程序可以使用框架來創(chuàng)建線程池實現(xiàn)接口。死亡線程方法執(zhí)行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。 超詳細的Java面試題總結(一)之Java基本知識 多線程和Java虛擬機 創(chuàng)建線程有幾種不同的方式?你喜歡哪一種?為什么? 繼承Thread類 實現(xiàn)R...
摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗,所以線程相關的問題在面試中經(jīng)常會被提到。將對象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對象稱之為反序列化。 JVM 內(nèi)存溢出實例 - 實戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細介紹 Java 注解的使用,有利于學習編譯時注解 Java 程序員快速上手 Kot...
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號作者架構師奮斗者掃描主頁左側二維碼,加入群聊,一起學習一起進步歡迎點贊收藏留言前情提要無意間聽到領導們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
摘要:最近在備戰(zhàn)面試的過程中,整理一下面試題。成員變量如果沒有被賦初值,則會自動以類型的默認值而賦值一種情況例外被修飾但沒有被修飾的成員變量必須顯示地賦值而局部變量則不會自動賦值。 最近在備戰(zhàn)面試的過程中,整理一下面試題。大多數(shù)題目都是自己手敲的,網(wǎng)上也有很多這樣的總結。自己感覺總是很亂,所以花了很久把自己覺得重要的東西總結了一下。 面向?qū)ο蠛兔嫦蜻^程的區(qū)別 面向過程: 優(yōu)點:性能比面...
閱讀 1533·2021-11-17 09:33
閱讀 3121·2021-10-13 09:39
閱讀 2792·2021-10-09 10:01
閱讀 2533·2021-09-29 09:35
閱讀 4051·2021-09-26 10:01
閱讀 3594·2019-08-26 18:37
閱讀 3244·2019-08-26 13:46
閱讀 1975·2019-08-26 13:39