- 說明類 java.lang.ThreadLocal 的作用和原理。列舉在哪些
程序中見過 ThreadLocal 的使用?
作用:
要編寫一個多線程安全(Thread-safe)的程序是困難的,為了讓線程共享資源,
必須小心地對共享資源進行同步,同步帶來一定的效能延遲,而另一方面,
在處理同步的時候,又要注意對象的鎖定與釋放,避免產生死結,種種因素
都使得編寫多線程程序變得困難。
嘗試從另一個角度來思考多線程共享資源的問題,既然共享資源這么困難,
那么就干脆不要共享,何不為每個線程創造一個資源的復本。將每一個線程
存取數據的行為加以隔離,實現的方法就是給予每個線程一個特定空間來保
管該線程所獨享的資源。
比如:在 Hibernate 中的 Session 就有使用。
ThreadLocal 的原理
ThreadLocal 是如何做到為每一個線程維護變量的副本的呢?其實實現的
思路很簡單,在 ThreadLocal 類中有一個 Map,用於存儲每一個線程的變
量的副本。 - 說說樂觀鎖與悲觀鎖
答:
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時
候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這
個數據就會 block 直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這
種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時
候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此
期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多
讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似於
write_condition 機制的其實都是提供的樂觀鎖。
兩種鎖各有優缺點,不可認為一種好於另一種,像樂觀鎖適用於寫比較
少的情況下,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大
了系統的整個吞吐量。但如果經常產生沖突,上層應用會不斷的進行 retry,
這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。 - 在 Java 中怎么實現多線程?描述線程狀態的變化過程。
答:當多個線程訪問同一個數據時,容易出現線程安全問題,需要某種方式
來確保資源在某一時刻只被一個線程使用。需要讓線程同步,保證數據安全
線程同步的實現方案:同步代碼塊和同步方法,均需要使用 synchronized
關鍵字
同步代碼塊:public void makeWithdrawal(int amt) {
synchronized (acct) { } }
同步方法:public synchronized void makeWithdrawal(int amt) { }
線程同步的好處:解決了線程安全問題
線程同步的缺點:性能下降,可能會帶來死鎖 - 請寫出多線程代碼使用 Thread 或者 Runnable,並說出兩種
的區別。
方式 1:繼承 Java.lang.Thread 類,並覆蓋 run() 方法。優勢:編寫簡單;
劣勢:無法繼承其它父類
public class ThreadDemo1 {
public static void main(String args[]) {
MyThread1 t = new MyThread1();
t.start();
while (true) {
System.out.println("兔子領先了,別驕傲");
} } }
class MyThread1 extends Thread {
public void run() {
while (true) {
System.out.println("烏龜領先了,加油");
} } }
方式 2:實現 Java.lang.Runnable 接口,並實現 run()方法。優勢:可繼承其
它類,多線程可共享同一個 Thread 對象;劣勢:編程方式稍微復雜,如需訪
問當前線程,需調用 Thread.currentThread()方法
public class ThreadDemo2 {
public static void main(String args[]) {
MyThread2 mt = new MyThread2();
Thread t = new Thread(mt);
t.start();
while (true) {
System.out.println("兔子領先了,加油");
} } }
class MyThread2 implements Runnable {
public void run() {
while (true) {
System.out.println("烏龜超過了,再接再厲");
} } } - 在多線程編程里,wait 方法的調用方式是怎樣的?
答:
wait 方法是線程通信的方法之一,必須用在 synchronized 方法或者
synchronized 代碼塊中,否則會拋出異常,這就涉及到一個“鎖”的概念,
而 wait 方法必須使用上鎖的對象來調用,從而持有該對象的鎖進入線程等
待狀態,直到使用該上鎖的對象調用 notify 或者 notifyAll 方法來喚醒之前
進入等待的線程,以釋放持有的鎖。 - Java 線程的幾種狀態
答:
線程是一個動態執行的過程,它有一個從產生到死亡的過程,共五種狀態:
尚學堂 Java 面試題大全及參考答案
新建(new Thread)
當創建 Thread 類的一個實例(對象)時,此線程進入新建狀態(未被啟動)
例如:Thread t1=new Thread();
就緒(runnable)
線程已經被啟動,正在等待被分配給 CPU 時間片,也就是說此時線程正在
就緒隊列中排隊等候得到 CPU 資源。例如:t1.start();
運行(running)
線程獲得 CPU 資源正在執行任務(run()方法),此時除非此線程自動放棄
CPU 資源或者有優先級更高的線程進入,線程將一直運行到結束。
死亡(dead)
當線程執行完畢或被其它線程殺死,線程就進入死亡狀態,這時線程不可能
再進入就緒狀態等待執行。
自然終止:正常運行 run()方法后終止
異常終止:調用 stop()方法讓一個線程終止運行
堵塞(blocked)
由於某種原因導致正在運行的線程讓出 CPU 並暫停自己的執行,即進入堵
塞狀態。
正在睡眠:用 sleep(long t) 方法可使線程進入睡眠方式。一個睡眠着的線
程在指定的時間過去可進入就緒狀態。
正在等待:調用 wait()方法。(調用 motify()方法回到就緒狀態)
被另一個線程所阻塞:調用 suspend()方法。(調用 resume()方法恢復) - 在 Java 多線程中,請用下面哪種方式不會使線程進入阻塞狀
態()
A sleep()
B. Suspend()
C. wait()
D. yield()
答案:D
分析:
yield 會是線程進入就緒狀態 - volatile 關鍵字是否能保證線程安全?
答:
不能。雖然 volatile 提供了同步的機制,但是知識一種弱的同步機制,如需
要強線程安全,還需要使用 synchronized。
Java 語言提供了一種稍弱的同步機制,即 volatile 變量,用來確保將變量
的更新操作通知到其他線程。當把變量聲明為 volatile 類型后,編譯器與運行時
都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內存操作一起
重排序。volatile 變量不會被緩存在寄存器或者對其他處理器不可見的地方,因
此在讀取 volatile 類型的變量時總會返回最新寫入的值。
一、volatile 的內存語義是:
當寫一個 volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量
值立即刷新到主內存中。
當讀一個 volatile 變量時,JMM 會把該線程對應的本地內存設置為無效,
直接從主內存中讀取共享變量。
二、volatile 底層的實現機制
如果把加入 volatile 關鍵字的代碼和未加入 volatile 關鍵字的代碼都生成匯
編代碼,會發現加入 volatile 關鍵字的代碼會多出一個 lock 前綴指令。
1 、重排序時不能把后面的指令重排序到內存屏障之前的位置
2、使得本 CPU 的 Cache 寫入內存 3、寫入動作也會引起別的 CPU 或者別的內核無效化其 Cache,相當於讓
新寫入的值對別的線程可見。 - 請寫出常用的 Java 多線程啟動方式,Executors 線程池有幾
種常用類型?
(1) 繼承 Thread 類 public class java_thread extends Thread{
public static void main(String args[]) {
new java_thread().run();
System.out.println("main thread run ");
}
public synchronized void run() {
System.out.println("sub thread run ");
}
}
(2) 實現 Runnable 接口
public class java_thread implements Runnable{
public static void main(String args[]) {
new Thread(new java_thread()).start();
System.out.println("main thread run ");
}
public void run() {
System.out.println("sub thread run ");
}
}在 Executor 框架下,利用 Executors 的靜態方法可以創建三種類型的常用線程
池:1)FixedThreadPool 這個線程池可以創建固定線程數的線程池。
2)SingleThreadExecutor 是使用單個 worker 線程的 Executor。 3)CachedThreadPool 是一個”無限“容量的線程池,它會根據需要創建
新線程。 - 關於 sleep()和 wait(),以下描述錯誤的一項是()
A. sleep 是線程類(Thread)的方法,wait 是 Object 類的方法
B. Sleep 不釋放對象鎖,wait 放棄對象鎖
C. Sleep 暫停線程、但監控狀態任然保持,結束后會自動恢復
D. Wait 后進入等待鎖定池,只針對此對象發出 notify 方法后獲取對象
鎖進入運行狀態。
答案:D
分析:
針對此對象的 notify 方法后獲取對象鎖並進入就緒狀態,而不是運行狀態。
另外針對此對象的 notifyAll 方法后也可能獲取對象鎖並進入就緒狀態,而
不是運行狀態 - 進程和線程的區別是什么?
進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程
是系統進行資源分配和調度的一個獨立單位.
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能
獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中
必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程
的其他的線程共享進程所擁有的全部資源.
區別 進程 線程
根本區別 作為資源分配的單位 調度和執行的單位
開銷 每個進程都有獨立的代碼和
數據空間(進程上下文),進程
間的切換會有較大的開銷。
線程可以看成時輕量級的進程,同一類
線程共享代碼和數據空間,每個線程有
獨立的運行棧和程序計數器(PC),線程
切換的開銷小。
所處環境 在操作系統中能同時運行多 在同一應用程序中有多個順序流同時
個任務(程序) 執行
分配內存 系統在運行的時候會為每個
進程分配不同的內存區域
除了 CPU 之外,不會為線程分配內存
(線程所使用的資源是它所屬的進程
的資源),線程組只能共享資源
包含關系 沒有線程的進程是可以被看
作單線程的,如果一個進程內
擁有多個線程,則執行過程不
是一條線的,而是多條線(線
程)共同完成的。
線程是進程的一部分,所以線程有的時
候被稱為是輕權進程或者輕量級進程。 - 以下鎖機機制中,不能保證線程安全的是()
A. Lock
B. Synchronized
C. Volatile
答案:C - 創建 n 多個線程,如何保證這些線程同時啟動?看清,是“同
時”。
答:用一個 for 循環創建線程對象,同時調用 wait()方法,讓所有線程
等待;直到最后一個線程也准備就緒后,調用 notifyAll(), 同時啟動所有線
程 - 同步和異步有何異同,在什么情況下分別使用它們?
答:
1.如果數據將在線程間共享。例如正在寫的數據以后可能被另一個線程讀到,
或者正在讀的數據可能已經被另一個線程寫過了,那么這些數據就是共享數
據,必須進行同步存取。
2.當應用程序在對象上調用了一個需要花費很長時間來執行的方法,並且不
希望讓程序等待方法的返回時,就應該使用異步編程,在很多情況下采用異
步途徑往往更有效率。
3.舉個例子: 打電話是同步 發消息是異步 - Java 線程中,sleep()和 wait()區別
答:
sleep 是線程類(Thread)的方法;作用是導致此線程暫停執行指定時間,給
執行機會給其他線程,但是監控狀態依然保持,到時后會自動恢復;調用
sleep()不會釋放對象鎖。
wait 是 Object 類的方法;對此對象調用 wait 方法導致本線程放棄對象鎖,
進入等 待此對象的等待鎖定池。只有針對此對象發出 notify 方法(或
notifyAll)后本線程才進入對象鎖定池,准備獲得對象鎖進行運行狀態。 - 下面所述步驟中,是創建進程做必須的步驟是()
A 由調度程序為進程分配 CPU
B. 建立一個進程控制塊
C. 為進程分配內存
D. 為進程分配文件描述符
答案:BC - 無鎖化編程有哪些常見方法?()
A 針對計數器,可以使用原子加
B. 只有一個生產者和一個消費者,那么就可以做到免鎖訪問環形緩沖區
(Ring Buffer)
C. RCU(Read-Copy-Update),新舊副本切換機制,對於舊副本可以
采用延遲釋放的做法
D. CAS(Compare-and-Swap),如無鎖棧,無鎖隊列等待
答案:D
分析:
A 這方法雖然不太好,但是常見
B ProducerConsumerQueue就是這個,到處都是
C linux kernel里面大量使用
D 本質上其實就是樂觀鎖,操作起來很困難。。單生產者多消費者或者多
生產者單消費者的情況下比較常見,也不容易遇到 ABA 問題。 - sleep()和 yield()有什么區別?
答:
① sleep()方法給其他線程運行機會時不考慮線程的優先級,因此會給低優
先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線
程以運行的機會;
② 線程執行 sleep()方法后轉入阻塞(blocked)狀態,而執行 yield()方法
后轉入就緒(ready)狀態;
③ sleep()方法聲明拋出 InterruptedException,而 yield()方法沒有聲明任
何異常;
④ sleep()方法比 yield()方法(跟操作系統相關)具有更好的可移植性。 - 當一個線程進入一個對象的 synchronized 方法 A 之后,其
它線程是否可進入此對象的 synchronized 方法?
答:不能。其它線程只能訪問該對象的非同步方法,同步方法則不能進入。
只有等待當前線程執行完畢釋放鎖資源之后,其他線程才有可能進行執行該
同步方法!
延伸 對象鎖分為三種:共享資源、this、當前類的字節碼文件對象 - 請說出與線程同步相關的方法。
答: - wait():使一個線程處於等待(阻塞)狀態,並且釋放所持有的對象的鎖;
- sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此
方法要捕捉 InterruptedException 異常; - notify():喚醒一個處於等待狀態的線程,當然在調用此方法的時候,並不
能確切的喚醒某一個等待狀態的線程,而是由 JVM 確定喚醒哪個線程,而
且與優先級無關; - notityAll():喚醒所有處入等待狀態的線程,注意並不是給所有喚醒線程一
個對象的鎖,而是讓它們競爭; - JDK 1.5 通過 Lock 接口提供了顯式(explicit)的鎖機制,增強了靈活性以
及對線程的協調。Lock 接口中定義了加鎖(lock())和解鎖(unlock())的方
法,同時還提供了 newCondition()方法來產生用於線程之間通信的
Condition 對象; - JDK 1.5 還提供了信號量(semaphore)機制,信號量可以用來限制對某個
共享資源進行訪問的線程的數量。在對資源進行訪問之前,線程必須得到信
號量的許可(調用 Semaphore 對象的 acquire()方法);在完成對資源的
訪問后,線程必須向信號量歸還許可(調用 Semaphore 對象的 release()
方法) - 編寫多線程程序有幾種實現方式?
答:Java 5 以前實現多線程有兩種實現方法:一種是繼承 Thread 類;另一
種是實現 Runnable 接口。兩種方式都要通過重寫 run()方法來定義線程的
行為,推薦使用后者,因為 Java 中的繼承是單繼承,一個類有一個父類,
如果繼承了 Thread 類就無法再繼承其他類了,同時也可以實現資源共享,
顯然使用 Runnable 接口更為靈活。
補充:Java 5 以后創建線程還有第三種方式:實現 Callable 接口,該接口中
的 call 方法可以在線程執行結束時產生一個返回值 - synchronized 關鍵字的用法?
答:synchronized 關鍵字可以將對象或者方法標記為同步,以實現對對象
和方法的互斥訪問,可以用 synchronized(對象) { … }定義同步代碼塊,或
者在聲明方法時將 synchronized 作為方法的修飾符。在第 60 題的例子中
已經展示了 synchronized 關鍵字的用法。 - 啟動一個線程是用 run()還是 start()方法?
答:啟動一個線程是調用 start()方法,使線程所代表的虛擬處理機處於可運
行狀態,這意味着它可以由 JVM 調度並執行,這並不意味着線程就會立即
運行。run()方法是線程啟動后要進行回調(callback)的方法。
API 解釋如下: - 什么是線程池(thread pool)?
答:在面向對象編程中,創建和銷毀對象是很費時間的,因為創建一個對象
要獲取內存資源或者其它更多資源。在 Java 中更是如此,虛擬機將試圖跟
蹤每一個對象,以便能夠在對象銷毀后進行垃圾回收。所以提高服務程序效
率的一個手段就是盡可能減少創建和銷毀對象的次數,特別是一些很耗資源
的對象創建和銷毀,這就是"池化資源"技術產生的原因。線程池顧名思義就
是事先創建若干個可執行的線程放入一個池(容器)中,需要的時候從池中
獲取線程不用自行創建,使用完畢不需要銷毀線程而是放回池中,從而減少
創建和銷毀線程對象的開銷。
Java 5+中的 Executor 接口定義一個執行線程的工具。它的子類型即線程池
接口是 ExecutorService。要配置一個線程池是比較復雜的,尤其是對於線
程池的原理不是很清楚的情況下,因此在工具類 Executors 面提供了一些靜
態工廠方法,生成一些常用的線程池,如下所示:
• newSingleThreadExecutor:創建一個單線程的線程池。這個線程池只
有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個
唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池
保證所有任務的執行順序按照任務的提交順序執行。
• newFixedThreadPool:創建固定大小的線程池。每次提交一個任務就創
建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到
最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池
會補充一個新線程。
• newCachedThreadPool:創建一個可緩存的線程池。如果線程池的大小
超過了處理任務所需要的線程,那么就會回收部分空閑(60 秒不執行任
務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處
理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操
作系統(或者說 JVM)能夠創建的最大線程大小。
• newScheduledThreadPool:創建一個大小無限的線程池。此線程池支
持定時以及周期性執行任務的需求。
• newSingleThreadExecutor:創建一個單線程的線程池。此線程池支持
定時以及周期性執行任務的需求。
有通過 Executors 工具類創建線程池並使用線程池執行線程的代碼。如果希
望在服務器上使用線程池,強烈建議使用 newFixedThreadPool 方法來創
建線程池,這樣能獲得更好的性能。 - 線程的基本狀態以及狀態之間的關系?
答:
除去起始(new)狀態和結束(finished)狀態,線程有三種狀態,分別是:
就緒(ready)、運行(running)和阻塞(blocked)。其中就緒狀態代表
線程具備了運行的所有條件,只等待 CPU 調度(萬事俱備,只欠東風);
處於運行狀態的線程可能因為 CPU 調度(時間片用完了)的原因回到就緒
狀態,也有可能因為調用了線程的 yield 方法回到就緒狀態,此時線程不會
釋放它占有的資源的鎖,坐等 CPU 以繼續執行;運行狀態的線程可能因為
I/O 中斷、線程休眠、調用了對象的 wait 方法而進入阻塞狀態(有的地方也
稱之為等待狀態);而進入阻塞狀態的線程會因為休眠結束、調用了對象的
notify 方法或 notifyAll 方法或其他線程執行結束而進入就緒狀態。注意:
調用 wait 方法會讓線程進入等待池中等待被喚醒,notify 方法或 notifyAll
方法會讓等待鎖中的線程從等待池進入等鎖池,在沒有得到對象的鎖之前,
線程仍然無法獲得 CPU 的調度和執行。 - 簡述 synchronized 和 java.util.concurrent.locks.Lock
的異同?
答:Lock 是 Java 5 以后引入的新的 API,和關鍵字 synchronized 相比主
要相同點:Lock 能完成 synchronized 所實現的所有功能;主要不同點:
Lock 有比 synchronized 更精確的線程語義和更好的性能。synchronized
會自動釋放鎖,而 Lock 一定要求程序員手工釋放,並且必須在 finally 塊
中釋放(這是釋放外部資源的最好的地方) - 創建線程的兩種方式分別是什么,優缺點是什么?
方式 1:繼承 Java.lang.Thread 類,並覆蓋 run() 方法。
優勢:編寫簡單;
劣勢:單繼承的限制----無法繼承其它父類,同時不能實現資源共享。
方式 2:實現 Java.lang.Runnable 接口,並實現 run()方法。
優勢:可繼承其它類,多線程可共享同一個 Thread 對象;
劣勢:編程方式稍微復雜,如需訪問當前線程,需調用
Thread.currentThread()方法 - Java 創建線程后,調用 start()方法和 run()的區別
兩種方法的區別
1) start 方法:
用 start 方法來啟動線程,真正實現了多線程運行,這時無需等待 run
方法體代碼執行完畢而直接繼續執行下面的代碼。通過調用 Thread 類的
start()方法來啟動一個線程,這時此線程處於就緒(可運行)狀態,並沒有
運行,一旦得到 cpu 時間片,就開始執行 run()方法,這里方法 run()稱為線
程體,它包含了要執行的這個線程的內容,Run 方法運行結束,此線程隨即
終止。
2) run():
run()方法只是類的一個普通方法而已,如果直接調用 run 方法,程序
中依然只有主線程這一個線程,其程序執行路徑還是只有一條,還是要順序
執行,還是要等待,run 方法體執行完畢后才可繼續執行下面的代碼,這樣
就沒有達到寫線程的目的。
總結:調用 start 方法方可啟動線程,而 run 方法只是 thread 的一個
普通方法調用,還是在主線程里執行。這兩個方法應該都比較熟悉,把需要
並行處理的代碼放在 run()方法中,start()方法啟動線程將自動調用 run()
方法,這是由 jvm 的內存機制規定的。並且 run()方法必須是 public 訪問權
限,返回值類型為 void。
兩種方式的比較 :
實際中往往采用實現 Runable 接口,一方面因為 java 只支持單繼承,
繼承了 Thread 類就無法再繼續繼承其它類,而且 Runable 接口只有一個
run 方法;另一方面通過結果可以看出實現 Runable 接口才是真正的多線程。 - 線程的生命周期
線程是一個動態執行的過程,它也有一個從產生到死亡的過程。
生命周期的五種狀態
新建(new Thread)
當創建 Thread 類的一個實例(對象)時,此線程進入新建狀態(未被
啟動)
例如:Thread t1=new Thread();
就緒(runnable)
線程已經被啟動,正在等待被分配給 CPU 時間片,也就是說此時線程
由於某種原因導致正在運行的線程讓出 CPU 並暫停自己的執行,即進
入堵塞狀態。
正在睡眠:用 sleep(long t) 方法可使線程進入睡眠方式。一個睡眠着
的線程在指定的時間過去可進入就緒狀態。
正在等待:調用 wait()方法。(調用 motify()方法回到就緒狀態)
被另一個線程所阻塞:調用 suspend()方法。(調用 resume()方法恢
復) - 如何實現線程同步?
當多個線程訪問同一個數據時,容易出現線程安全問題,需要某種方式
來確保資源在某一時刻只被一個線程使用。需要讓線程同步,保證數據安全
線程同步的實現方案:
1)同步代碼塊,使用 synchronized 關鍵字
同步代碼塊:
synchronized (同步鎖) {
授課代碼; }
同步方法:
public synchronized void makeWithdrawal(int amt) {}
線程同步的好處:解決了線程安全問題
線程同步的缺點:性能下降,可能會帶來死鎖
注意: 同步代碼塊,所使用的同步鎖可以是三種,
1、 this 2、 共享資源 3、 字節碼文件對象
同步方法所使用的同步鎖,默認的是 this - 說說關於同步鎖的更多細節
答:
Java 中每個對象都有一個內置鎖。
當程序運行到非靜態的 synchronized 同步方法上時,自動獲得與正在執行
代碼類的當前實例(this 實例)有關的鎖。獲得一個對象的鎖也稱為獲取鎖、
鎖定對象、在對象上鎖定或在對象上同步。
當程序運行到 synchronized 同步方法或代碼塊時才該對象鎖才起作用。
一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以
獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味着任何其他線程都不
能進入該對象上的 synchronized 方法或代碼塊,直到該鎖被釋放。
釋放鎖是指持鎖線程退出了 synchronized 同步方法或代碼塊。
關於鎖和同步,有一下幾個要點:
1)只能同步方法,而不能同步變量和類;
2)每個對象只有一個鎖;當提到同步時,應該清楚在什么上同步?也就是
說,在哪個對象上同步?
3)不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)如果兩個線程要執行一個類中的 synchronized 方法,並且兩個線程使
用相同的實例來調用方法,那么一次只能有一個線程能夠執行方法,另一個
需要等待,直到鎖被釋放。也就是說:如果一個線程在對象上獲得一個鎖,
就沒有任何其他線程可以進入(該對象的)類中的任何一個同步方法。
5)如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪
問而不受鎖的限制。
6)線程睡眠時,它所持的任何鎖都不會釋放。
7)線程可以獲得多個鎖。比如,在一個對象的同步方法里面調用另外一個
對象的同步方法,則獲取了兩個對象的同步鎖。
8)同步損害並發性,應該盡可能縮小同步范圍。同步不但可以同步整個方
法,還可以同步方法中一部分代碼塊。
9)在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要獲取
哪個對象的鎖。 - Java 中實現線程通信的三個方法的作用是什么?
Java 提供了 3 個方法解決線程之間的通信問題,均是 java.lang.Object 類
的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出異常。
方法名 作 用
final void wait() 表示線程一直等待,直到其它線程通知
void wait(long
timeout)
線程等待指定毫秒參數的時間
final void wait(long
timeout,int nanos)
線程等待指定毫秒、微妙的時間
final void notify()
喚醒一個處於等待狀態的線程。注意的是
在調用此方法的時候,並不能確切的喚醒
某一個等待狀態的線程,而是由 JVM 確定
喚醒哪個線程,而且不是按優先級。
final void notifyAll()
喚醒同一個對象上所有調用 wait()方法的
線程,注意並不是給所有喚醒線程一個對
象的鎖,而是讓它們競爭
正在就緒隊列中排隊等候得到 CPU 資源。例如:t1.start();
運行(running)
線程獲得 CPU 資源正在執行任務(run()方法),此時除非此線程自動放
棄 CPU 資源或者有優先級更高的線程進入,線程將一直運行到結束。
死亡(dead)
當線程執行完畢或被其它線程殺死,線程就進入死亡狀態,這時線程不
可能再進入就緒狀態等待執行。
自然終止:正常運行 run()方法后終止
異常終止:調用 stop()方法讓一個線程終止運行
堵塞(blocked)