Java多線程機制
一、線程同步
- synchronized(this){…},鎖定當前對象,在執行大括號裏面的語句時不會被打斷或者說一個線程執行過程中不會被另一個線程打斷。synchronized作用就是鎖定一段代碼,當執行這段代碼時鎖定當前對象,另外想要訪問此對象的線程,只能等當前線程執行完畢後才能訪問。還可以寫成還可以寫成public synchronized void 方法名稱(參數列表){…},意思是在執行此方法過程之中的當前對象被鎖定。
示例:
package threadtest;
public class TestSync implements Runnable {
Timer timer = new Timer();
public static void main(String[] args) {
TestSync testSync = new TestSync();
Thread t1 = new Thread(testSync);
Thread t2 = new Thread(testSync);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
@Override
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer {
private static int number = 0;
public synchronized void add(String name) {
// synchronized (this) {
number++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ",你是第" + number + "個使用timber的線程");
// }
}
}
未加synchronized之前的結果如下圖:
之所以會出現這樣的結果,兩個線程訪問了同樣的的timer對象,是因爲當t1線程執行add方法後首先number變成了1,然後t1線程睡眠了1毫秒,此時t2線程開始執行,number變成2,t1線程開始醒過來,輸出number爲2,t2線程醒過來,輸出number也爲2(加入sleep方法只是爲了放大效果,即使不加,程序也有可能會出現上面的問題)。
== 加入synchronized之後的輸出結果如下:==
可以看到加入synchronized之後,t1就鎖定了當前對象,不允許其他線程在對timber對象訪問,t1線程執行完畢後t2線程纔開始執行。
- 在Java語言中,引入了對象互斥鎖的概念,保證共享數據操作的完整性。每個對象都對應一個可稱爲“互斥鎖”的標記,這個標記保證在同一時刻只能有一個線程訪問該對象。
- 關鍵字synchronized與對象的互斥鎖聯繫。當某個對象被synchronized修飾時,表明該對象在同一時刻只能由一個線程訪問。
synchronized的使用方法:
synchronized(this){
num++;
try{
Thread.sleep(1);
}
catch(InterruptedException e){
...
}
System.out.println(name + ", 你是第' + “number” 個使用timer的線程)
}
}
synchronized還可以放在方法的聲明中,表示整個方法爲同步方法,即方法裏面的當前對象被鎖定
synchronized public void add(String name){...}
二、死鎖問題
- 概念:a線程首先鎖定了對象o1,然後鎖定了對象o2,最後輸出某些信息,b線程首先鎖定了對象o2,然後鎖定對象o1,最後輸出信息,a線程想要正常執行必須等待b線程釋放對象o2,才能鎖定o2,而b線程想要正常執行必須等待a線程釋放對象o1,它才能鎖定o1,這樣就造成了程序的死鎖問題。示例代碼如下:
package threadtest;
public class TestDeadLock implements Runnable {
public int flag = 1;
static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
System.out.println("flag= " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
TestDeadLock testDeadLock1 = new TestDeadLock();
TestDeadLock testDeadLock2 = new TestDeadLock();
testDeadLock1.flag = 1;
testDeadLock2.flag = 0;
Thread t1 = new Thread(testDeadLock1);
Thread t2 = new Thread(testDeadLock2);
t1.start();
t2.start();
}
}
程序輸出如下:
從運行結果可以看到,程序已經“死了”,“0”和“1”都不會輸出。
- 如果一個方法做了同步,而另外一個方法沒有做同步,那麼別的線程可以訪問那個非同步的方法,並且可能會對同步的按個方法產生影響。給當前對象加鎖(或成爲同步),結果是那些非加鎖(同步)的方法還可以訪問,加了鎖的方法只是說明另外一個線程不能在同一個時刻再訪問。互斥指在某一個時間段保證只有一個線程進入方法中,但不保證其他的線程是不是能進到另一個方法裏面(概念性的東西,我聽着也暈,瞭解即可…)
三、wait和sleep的區別
- wait時別的線程可以訪問鎖定對象(調用wait的時候必需鎖定該對象(用synchronized),否則是無法調用的),wait一般和notify或者notifyAll一起使用,用於喚醒線程。
- sleep時別的線程是無法訪問鎖定對象的,即線程睡眠過程中仍然持有鎖。sleep方法,在睡眠結束後會自動喚醒。