java多線程理解 以及java實現的簡單的死鎖

     多線程,三大機制中的一個,編程問題的一個難點,在前兩天的面試中,就被問到了這個問題,尷尬的是當時居然沒回答上來,最後還被刷了,想起來還真是尷尬。

     說到多線程,先來理解一下線程吧。

     線程,也被稱爲“輕量級進程”,是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己幾乎不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以併發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒阻塞運行。

   線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制

從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

在java中實現多線程有兩種方式:繼承Runnable接口和繼承Thread方法,無論哪種方式實現多線程都需要建立Thread類或它子類的實例,例如一個簡單的線程。

第一種方式:

public class Car1 implements Runnable {

    @Override
    public void run() {
        System.out.println("我是第一種方式創建的線程!");
    }
    
    public static void main(String[] args) {
        Runnable r = new Car1();
        Thread t = new Thread(r);
        t.start();
    }

}

第二種方式:

public class Car2 extends Thread {
    @Override
    public void run(){
        System.out.println("我是第二種方式創建的線程!");
    }
    
    public static void main(String[] args) {
        Thread t = new Car2();
        t.start();
    }
}

 兩種方式都需要重寫run()方法,以實現自己的方法。其次啓動線程都需要用start()方法,若直接調用run方法時,則只會有一個線程啓動,不能實現多線程。

實現多線程:

package mhc.learn.Thread;

public class Car1 implements Runnable {

    @Override
    public void run() {
        //Thread.currentThread().getName()獲取當前線程的名字
        System.out.println("我是小汽車線程==="+Thread.currentThread().getName()+"===我在跑。o 0");
    }
    
    public static void main(String[] args) {
        Runnable r = new Car1();
//        Thread t = new Thread(r);
//        t.start();
        //嘗試啓動多個線程
        Thread t[] = new Thread[100];
        for(int i=0;i<100;i++){
            t[i] = new Thread(r,"線程"+i);//給每個線程不同的名字,加以區別
        }
        
        for(Thread thread:t){
            thread.start();
        }
        
    }

}
該程序的運行結果:

從運行的程序結果可以看出,運行的線程都是搶佔式的,可以說他們的順序都是隨機的,所以再這種情況下就會出現一些問題,例如對公共變量的操作。


就像下面這樣

public class Car1 implements Runnable {
	static int sum=0;//創建一個統計線程總數的變量,這個變量是該類對象共享的,每個線程都可以對這個數字進行操作
	@Override
	public void run() {
		//Thread.currentThread().getName()獲取當前線程的名字
		sum++;
		System.out.println("我是小汽車線程==="+Thread.currentThread().getName()+"===我在跑。o 0");
	}
	
	public static void main(String[] args) {
		Runnable r = new Car1();
//		Thread t = new Thread(r);
//		t.start();
		//嘗試啓動多個線程
		Thread t[] = new Thread[100];
		for(int i=0;i<100;i++){
			t[i] = new Thread(r,"線程"+i);//給每個線程不同的名字,加以區別
		}
		
		for(Thread thread:t){
			thread.start();
		}
		System.out.println("線程的總數是:"+Car1.sum);	
	}
}
//結果獲得的線程的總數是90,99,93等等,這個數字肯定是比真實的線程總數100少的,這個是可以解釋的,當第一個線程取得該值的時候假如sum=1,第二個線程也取得了sum=1,然後第一個線程保存了sum++的結果2,但第二個線程此時也寫入了sum=2,所以兩個線程操作完sum之後還是2,在這種情況之下就出現了差錯。所以加鎖就很容易想得到了。

Synchronized關鍵字,就是同步鎖,關於同步鎖的上鎖方式的介紹,我沒寫博文,我看到有一篇寫的很詳細的推薦一下:http://www.blogjava.net/konhon/archive/2005/08/17/10296.html,謝謝仁兄的博文,


按博文中的第4個將run方法修改爲下

public void run() {
        synchronized(Car1.class){
            //Thread.currentThread().getName()獲取當前線程的名字
            sum++;
            System.out.println("我是小汽車線程==="+Thread.currentThread().getName()+"===我在跑。o 0");
        }
    }

然後在主線程中將主線程睡眠一小段時間,防止主線程和其他線程一起搶佔CPU資源,實現的代碼爲

try {
            Thread.currentThread().sleep(500);
            //Thread.currentThread().join();//當前的主線程將等待其他線程全部執行完之後才執行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        System.out.println("線程的總數是:"+Car1.sum);
        這樣一來,可以統計出當前的所有總線程數爲100.

在想讓主線程最後執行的過程中,在網上搜索了很多資料,發現很多都是推薦使用當前線程的join()方法來實現,

但是有的卻說,是使用主線程的join,這樣可以保證主線程等待其他線程執行完畢之後再執行,還有的說要在每個線程都得加上join方法,Thread最後不加,這樣才能保證要求。到底如何呢?我也打算自己來試一試,第一種:主線程中加入join()方法後,join方法後面的一句話未輸出,而程序一直結束不了,不知道是什麼原因。

第二種:程序運行結果顯示,主線程最後一句輸出並不是最後執行的,沒有達到效果。那到底是怎麼回事??

第二天我查找了相關的資料,找到了,器其實第一種是錯誤的,第二種是正確的,join方法應該是用在線程啓動之後,在啓動之前使用join方法是沒用的。






來說說java實現的死鎖吧,synchronized這個關鍵字肯定是會用到的,那次面試中我也很詫異,當時還沒反應過來,給面試官寫的java簡單的死鎖還沒用到synchronzied這個關鍵字,我也是醉了,回來之後,看了一些網上的參考程序,過段時間又快忘記了。還是總結下來的好。

老規矩,上程序,解釋已經註釋在程序中。



着下面是第一次的

package com.mhc.learn;



/**
 * 
 * @author mhc
 *練習java做一個死鎖,上一次面試的時候居然沒寫出來
 */
public class deadLock implements Runnable{


public int flag=0; //設置變量使多個線程的鎖定靜態變量的順序不一樣

static Object a = new Object(),b=new Object();//****需要靜態的類的變量


@Override
public void run() {

System.out.println("flag = " + flag);
if(flag==1){//當爲1時先鎖0.5sa對象,再鎖b對象
synchronized (a) {
try {
// System.out.println("a當前的線程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized (b) {
System.out.println("      1     ");
// System.out.println("b當前的線程:"+Thread.currentThread().getName());
}
}


if(flag==0){//鎖對象的順序與1相反
synchronized (b) {
try {
// System.out.println("b當前的線程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized (a) {
System.out.println("   0   ");
// System.out.println("a當前的線程:"+Thread.currentThread().getName());
}
}

}

public static void main(String[] args) {
deadLock d1 = new deadLock();
deadLock d2 = new deadLock();
d1.flag = 0;
d2.flag = 1;
for (int i = 0; i < 10; i++) {
new Thread(d1).start();
new Thread(d2).start();
}

}


}



上面的代碼,死鎖的效果不明顯,最後又參考了些資料。發現另外一種更明顯的死鎖寫法,就是在鎖a的函數裏面鎖b,改成嵌套的,而不是順序的,就更容易鎖住

代碼:

package com.mhc.learn;


import java.util.concurrent.atomic.AtomicInteger;


/**
 * 
 * @author mhc
 *練習java做一個死鎖,上一次面試的時候居然沒寫出來
 */
public class deadLock implements Runnable{


public int flag=0; //設置變量使多個線程的鎖定靜態變量的順序不一樣

static Object a = new Object(),b=new Object();//需要靜態的類的變量




@Override
public void run() {

System.out.println("flag = " + flag);
if(flag==1){
synchronized (a) {
try {
// System.out.println("a當前的線程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
synchronized (b) {
System.out.println("      1     ");
// System.out.println("b當前的線程:"+Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}


}


if(flag==0){
synchronized (b) {
try {
// System.out.println("b當前的線程:"+Thread.currentThread().getName());
Thread.currentThread().sleep(500);
synchronized (a) {
System.out.println("   0   ");
// System.out.println("a當前的線程:"+Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}


}

}

public static void main(String[] args) {
deadLock d1 = new deadLock();
deadLock d2 = new deadLock();
d1.flag = 0;
d2.flag = 1;
for (int i = 0; i < 10; i++) {
new Thread(d1).start();
new Thread(d2).start();
}

}


}




可發現程序一直未結束。成功的實現了死鎖!


總結一下,實現死鎖的幾個關鍵點,1、需要類靜態成員變量 2、需要區分進入不同對象鎖函數的標誌(比如本程序中的flag),3、創建多個線程更容易看到效果 4、需要加入線程睡眠時間.  5,還有就是對對象加鎖的順序。(基本數據類型不讓加鎖)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章