Thread類相關問題
1、start()方法和run()方法
呼叫start()方法會啟動新執行緒,新執行緒會自動執行run()方法,run()方法執行完畢,執行緒生命週期結束。start()方法不能被重複呼叫,否則拋異常程式終止。
run()方法是執行緒的執行體,可以與普通方法一樣被主動重複呼叫,單獨呼叫run()方法的話,會在當前執行緒中執行run(),而不會啟動新執行緒,啟動新執行緒需要start()方法。
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("currentThreadName=" + Thread.currentThread());
}
});
thread.run();//將run()方法當普通方法呼叫,不會建立新執行緒
thread.run();//將run()方法當普通方法呼叫,可以重複呼叫
thread.start();//建立新執行緒,並自動呼叫run()方法
//thread.start();//多次呼叫start()拋異常
}
執行結果:
Thread的start()方法原始碼
思考:為何start()方法要加synchronized關鍵字修飾,不加會有什麼問題?
2、synchronized關鍵字
在Java中,每個物件有且僅有一個同步鎖,意味著同步鎖依賴物件而存在。當執行緒訪問某物件的synchronized區域(synchronized方法或程式碼塊)時,其他執行緒對該物件的所有synchronized區域的訪問將被阻塞(因為方法或程式碼塊所屬的物件被鎖定)。而對該物件的非同步區域可以正常訪問。
synchronized關鍵字被編譯後,會在同步塊的前後分別形成monitorenter和monitorexit這兩個位元組碼指令,這兩個位元組碼都需要一個reference型別的引數來指明要鎖定和解鎖的物件。如果程式中synchronized明確指定了物件引數(synchronized修飾程式碼塊),那就是這個物件的reference;如果沒有明確指定(synchronized修飾的是方法),那就根據synchronized修飾的是例項方法還是類方法,去取對應的物件例項或者類物件作為鎖物件。
synchronized例項1
執行緒鎖定相同物件,其他執行緒對該synchronized區域不能訪問
class MyRunable implements Runnable {
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class ThreadSynchronized {
public static void main(String[] args) {
Runnable runnable = new MyRunable(); // 新建“Runnable物件”
Thread t1 = new Thread(runnable, "t1"); // 新建“執行緒t1”, t1是基於demo這個Runnable物件
Thread t2 = new Thread(runnable, "t2"); // 新建“執行緒t2”, t2是基於demo這個Runnable物件
t1.start(); // 啟動“執行緒t1”
t2.start(); // 啟動“執行緒t2”
}
}
執行結果
先將t1執行緒執行完,再執行t2執行緒。
因為synchronized(this)鎖定的是MyRunable類的物件例項runnable,而t1與t2兩個執行緒的執行體是同一個runnable物件,因此當t1鎖定時,t2並不能訪問,直到t1執行完畢釋放鎖,t2才能獲取鎖繼續執行。
synchronized示例2
執行緒鎖定相同物件,其他執行緒對該物件的其他synchronized區域也不能訪問
class Count {
// 含有synchronized同步塊的方法
public void synMethod() {
synchronized(this) {//鎖定Count物件例項
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 也包含synchronized同步塊的方法
public void synMethod2() {
synchronized(this) {//鎖定Count物件例項
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " synMethod2 loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class ThreadSynchronizedTwo {
public static void main(String[] args) {
final Count count = new Count();
// 新建t1, t1會呼叫“count物件”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
public void run() {
count.synMethod();
}
}, "t1");
// 新建t2, t2會呼叫“count物件”的nonSynMethod()方法
Thread t2 = new Thread(
new Runnable() {
public void run() {
count.synMethod2();
}
}, "t2");
t1.start(); // 啟動t1
t2.start(); // 啟動t2
}
}
執行結果
雖然兩個執行緒訪問的程式碼塊不同,但仍是先將t1執行緒執行完,再執行t2執行緒。
因為兩個執行緒鎖定的是同一個物件count,即使兩執行緒訪問不同的synchronized同步程式碼塊,t2仍需要等待t1執行完畢,釋放count物件鎖之後才能執行。
synchronized示例3
執行緒鎖定相同物件,其他執行緒對該物件的非synchronized區域可以訪問
class MyCount {
// 含有synchronized同步塊的方法
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 非同步的方法
public void nonSynMethod() {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
public class ThreadSynchronizedThree {
public static void main(String[] args) {
final MyCount count = new MyCount();
// 新建t1, t1會呼叫“count物件”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
public void run() {
count.synMethod();
}
}, "t1");
// 新建t2, t2會呼叫“count物件”的nonSynMethod()方法
Thread t2 = new Thread(
new Runnable() {
public void run() {
count.nonSynMethod();
}
}, "t2");
t1.start(); // 啟動t1
t2.start(); // 啟動t2
}
}
執行結果
t1與t2交替執行。
雖然t1保留了count物件鎖,但t2執行的是非synchronized同步區域,不需要等待物件鎖,所以t1獲取物件鎖,不影響t2的執行。
synchronized示例4
執行緒鎖定不同物件,對不同物件的synchronized區域可以訪問
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class ThreadSynchronizedFour {
public static void main(String[] args) {
Thread t1 = new MyThread("t1"); // 新建“執行緒t1”
Thread t2 = new MyThread("t2"); // 新建“執行緒t2”
t1.start(); // 啟動“執行緒t1”
t2.start(); // 啟動“執行緒t2”
}
}
執行結果
t1與t2交替執行。
因為t1與t2鎖定的是不同的MyThread物件例項,因此相互不影響。
3、例項鎖與全域性鎖
例項鎖:鎖在某個例項物件上,例項鎖對應的是synchronized關鍵字。
全域性鎖:鎖針對的是類,無論該類有多少物件,執行緒都共享該鎖,全域性鎖對應的是static synchronized關鍵字(或者是鎖在該類的class或者classloader物件上)。
假設程式碼如下:
pulbic class Something {
public synchronized void syncA(){}
public synchronized void syncB(){}
public static synchronized void staticSyncA(){}
public static synchronized void staticSyncB(){}
}
Something x = new Something();
Something y = new Something();
分析下面4組情況能否同時訪問:
(1)x.syncA與x.syncB
(2)x.syncA與y.syncA
(3)x.staticSyncA與y.staticSyncB
(4)x.syncA與Something.staticSyncB
抓住synchronized同步鎖依賴物件而存在,只要分析出鎖定的物件是否相同,不難分析出:
(1) 不能同時訪問,因為它們鎖定的物件相同,都是x物件例項;
(2) 可以同時訪問,因為它們鎖定的物件不同,分別是x物件例項和y物件例項;
(3) 不能同時訪問,因為它們鎖定的物件相同,都是Something類例項;
(4) 不能同時訪問,因為它們鎖定的物件不同,分別是x物件例項和Something類例項。
4、執行緒等待wait()與執行緒喚醒notify()
在Object.java中,定義了wait(), notify()和notifyAll()等介面。
wait()的作用是讓當前執行緒(CPU正在執行的執行緒)進入等待狀態,同時,wait()也會讓當前執行緒釋放它所持有的鎖(而阻塞在sleep()方法時不會釋放鎖)。
而notify()和notifyAll()的作用,則是喚醒當前物件上的等待執行緒;notify()是喚醒單個執行緒(如有多個執行緒等待,隨機喚醒其中一個執行緒),而notifyAll()是喚醒所有的執行緒。
//Wait()使當前執行緒等待
class TestThread extends Thread{
public TestThread(String name) {
super(name);
}
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" call notify()");
// 喚醒當前的wait執行緒
notify();
}
}
}
public class WhichThreadWait {
public static void main(String[] args) {
TestThread t1 = new TestThread("t1");
synchronized(t1) {
try {
// 啟動“執行緒t1”
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start();
// 主執行緒等待t1通過notify()喚醒。
System.out.println(Thread.currentThread().getName()+" wait()");
t1.wait();//為何這裡是主執行緒等待,而不是t1執行緒等待?
System.out.println(Thread.currentThread().getName()+" continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
執行結果:
t1.wait()為何這裡是主執行緒等待,而不是t1執行緒等待?
JDK中的解釋 “ Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. ”, wait()的作用是讓“當前執行緒”等待,即正在CPU上執行的執行緒。雖然t1.wait()是通過“執行緒物件t1”呼叫的wait()方法,但是執行t1.wait()的地方是在主執行緒,即主執行緒是當前執行緒,需要處於等待狀態。
喚醒所有在此監視器上等待的執行緒
public class WaitAndNotify {
private static Object obj = new Object();
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
MyThread t3 = new MyThread("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep(3000)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+" notifyAll()");
obj.notifyAll();//喚醒所有wait()執行緒
//obj.notify();//喚醒單個wait()執行緒
}
}
static class MyThread extends Thread{
public MyThread(String name){
super(name);
}
public void run() {
synchronized (obj) {
try {
// 列印輸出結果
System.out.println(Thread.currentThread().getName() + " wait");
//當前執行緒進入等待狀態,釋放鎖
obj.wait();
// 列印輸出結果
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
執行結果:
主執行緒通過notifyAll()喚醒所有等待的子執行緒。
思考: notify()是依據什麼喚醒等待執行緒的,即wait()等待執行緒與notify()之間通過什麼關聯起來的?為何notify(),wait()等方法是定義在Object中,而不是Thread中?
wait()與notify()方法的聯絡依據是物件同步鎖。使用wait()與notify()方法必須要有物件鎖,否則執行時拋非法監視器的異常。喚醒執行緒(負責喚醒等待執行緒的那個執行緒),只有在獲取該物件的同步鎖(必須與等待執行緒是同一個鎖),並且呼叫notify()或notifyAll()方法之後,才能喚醒等待執行緒。雖然等待執行緒被喚醒,但是它不能立即執行,需要等待喚醒執行緒釋放物件的同步鎖之後,等待執行緒才能獲取物件同步鎖進而繼續執行。
既然notify()與wait()依賴於同步鎖(不是依賴執行緒),而同步鎖是物件所持有,並且每個物件有且僅有一個。因此notify()與wait()等方法需要定義在Object()類,而不是Thread類中。
##5、執行緒同步之join()##
讓當前執行join()方法的執行緒等待,直到等待超時或擁有join()方法的執行緒執行完畢。
public class JoinTest{
public static void main(String[] args){
try {
ThreadA t1 = new ThreadA("t1"); // 新建“執行緒t1”
t1.start(); // 啟動“執行緒t1”
t1.join(); //“主執行緒main()會等待"執行緒t1"完成”
System.out.printf("%s finish\n", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public void run(){
System.out.printf("%s start\n", this.getName());
// 延時操作
try {
sleep(3000);
} catch (Exception e) {
// TODO: handle exception