Java多線程(1)

Java多線程(1)

一.實現方法

1.繼承Thread類,重寫run方法

實例代碼:

publicclass MyFirstThread extends Thread {

publicinti;

@Override

publicvoid run() {

while (i++ < 100) {

System.out.println(this.getName() + ":i=" + i);

}

}

publicstaticvoid main(String[] args) {

MyFirstThread thread1 = new MyFirstThread();

MyFirstThread thread2 = new MyFirstThread();

thread1.start();

thread2.start();

}

}

2.實現Runnable接口,傳給Thread實現方法。

實現實例

public class ThreadRunnableTest implements Runnable{

@Override

public void run() {

while (true) {

System.out.println("線程執行。。。。。。。");

}

}

public static void main(String[] args) {

new Thread(new ThreadRunnableTest()).start();

System.out.println("主線程執行。。。。。。");

}

}

二、線程的生命週期

新建,就緒,運行,阻塞和死亡狀態

·新建:線程被new出來就是新建狀態

·就緒:調用start方法,則進入就緒狀態,等待CPU的調度

·運行:得到CPU的時間片(可以理解爲時間片輪轉)則進入運行狀態

·阻塞:運行狀態中可能會被CPU調度下來(比如被掛起,調用sleep方法,調用一個阻塞方法比如getchar()之類的方法),進入阻塞狀態

·死亡:線程執行完畢或者因爲異常或者調用stop方法之後線程結束則進入死亡狀態。

·ALive狀態:是否死亡,是就緒、運行和阻塞三種狀態的合集。

·線程進入阻塞狀態的情況:

n 線程調用一個阻塞方法,方法返回前該線程一直阻塞。

n 線程調用sleep方法進入阻塞。

n 線程嘗試獲取同步監視器(常說的線程鎖),但該同步監視器被其他線程持有。

線程解除阻塞,重新進入就緒狀態的情況:

n 調用的阻塞方法返回。

n 調用的sleep到期。

n 線程成功獲取同步監視器(我們常說的線程鎖)

線程的死亡狀態就是線程的結束,線程結束的情況有如下幾種:

n run方法執行完成

n 線程拋出異常

n 直接調用線程的stop方法結束線程(該方法已經過時,並且無代替方法,不建議使用)

判斷線程是否死亡可以使用isAlive方法,當線程處於就緒、運行和阻塞三種狀態的時候返回true,否則返回false。另外,不能對已經死亡的線程重新調用start方法重新啓動。

另外,線程的suspend方法和stop方法非常容易導致死鎖,一般不推薦使用。

三.線程控制

join線程

當一個線程需要等待另一個線程完畢再執行的話,可以使用Threadjoin方法。

假設線程A和線程B,在A執行時調用了Bjoin方法(前提是B已經進入Alive狀態A將被阻塞,一直等到B線程執行完畢後,A線程繼續執行,就好像排隊加塞。

示例代碼:

publicclass TestJoinThread {

publicstaticvoid main(String[] args) {

MainThread mt = new MainThread();

mt.start();

}

}

class MainThread extends Thread {

publicvoid run() {

// TODO Auto-generated method stub

for (int i = 0; i < 50; i++) {

if (i == 30) {

JoinThread jt = new JoinThread();

jt.setName("join線程");

jt.start();

try {

jt.join();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

System.out.println(this.getName() + " " + i);

}

}

}

class JoinThread extends Thread {

@Override

publicvoid run() {

// TODO Auto-generated method stub

for (int i = 0; i < 50; i++) {

System.out.println(this.getName() + " " + i);

}

}

}

運行結果:

Thread-0 27

Thread-0 28

Thread-0 29

join線程 0

join線程 1

join線程 2

join線程 46

join線程 47

join線程 48

join線程 49

Thread-0 30

Thread-0 31

Thread-0 32

Thread-0 33

Thread-0 48

Thread-0 49

可以看到Main線程正常運行,調用了另外一個線程JoinThreadjoin方法,那麼Main線程等待JoinThread執行完畢後再執行。

線程睡眠

如果讓線程休息一會,我們可以使用Thread的靜態方法sleep(long millis)。這個方法的意思是:在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。

sleep方法會讓線程休息指定的毫秒數,在sleep的時候,不會放棄執行權力,等待休息時間到了,馬上獲取執行機會。也就是說,在這個線程sleep的時候,其他線程不會獲得CPU的執行權力。

但如果睡眠過程中會持有鎖的話,最好睡眠之前把鎖放棄(這是後話了)。

示例代碼:

publicclass TestThreadSleep {

publicstaticvoid main(String[] args) {

for (int i = 0; i < 50; i++) {

System.out.println(i);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

程序將每隔一秒打印一下變量i的值。

·線程讓步和線程優先級使用頻率很低,一方面因爲其隨機性對線程的控制弱,另一方面現在多核CPU的原因,所以不在總結。

線程安全

線程安全的概念不容易定義,在《Java 併發編程實踐》中,作者做出瞭如此定義:多個線程訪問一個類對象時,如果不用考慮這些線程在運行時環境下的調度和交替執行,並且不需要額外的同步及在調用方法代碼不必作其他的協調,這個類的行爲仍然是正確的,那麼這個類是線程安全的。

也就是說一堆線程去操作一個方法去控制同一個資源,由於是交替執行的,可能會出現一個數據一個線程正在運算還沒來得急把數據寫進去,結果被另外一個線程把這個數據的髒數據讀取出去了。

這樣說,可能有些朋友沒有看明白,那麼我們先來看一個示例,來演示一下什麼是現成不安全的情況。

publicclass SafeThreadTest {

V v = new V();

publicstaticvoid main(String[] args) {

SafeThreadTest test = new SafeThreadTest();

test.test();

}

/**

* 開兩個線程,分別調用V對象的打印字符串的方法

*/

publicvoid test(){

new Thread(new Runnable() {

@Override

publicvoid run() {

while(true){

v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

}

}

}).start();

new Thread(new Runnable() {

@Override

publicvoid run() {

while(true){

v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");

}

}

}).start();

}

/**

* 這個類負責打印字符串

* @author Administrator

*/

class V {

//創建一個鎖對象

Lock lock = new ReentrantLock();

/**

* 爲了能使方法運行速度減慢,我們一個字符一個字符的打印

* @param s

*/

publicvoid printString(String s){

//加鎖,只允許一個線程訪問

lock.lock();

try {

for(int i = 0;i<s.length();i++){

System.out.print(s.charAt(i));

}

System.out.println();

}finally{

//解鎖,值得注意的是,這裏鎖的釋放放到了finally代碼塊中,保證解鎖工作一定會執行

lock.unlock();

}

}

}

}

使用這樣的方式,與使用synchronized的功能一樣,只不過這樣使代碼看起來更加面向對象一些,怎麼加鎖,怎麼解鎖一目瞭然。

另外,ReentrantLock其實比synchronized增加了一些功能,主要有:

等待可中斷

這是指的當前持有鎖的線程如果長期不釋放鎖,正在等待的線程可以放棄等待,處理其他事情。

公平鎖

這個是說多個線程在等待同一個鎖的時候,必須按照申請鎖的時間順序依次獲得鎖。synchronized中的鎖是不公平鎖,鎖被釋放的時候任何一個等待鎖的線程都有機會獲得鎖,ReentrantLock默認也是不公平鎖,可以使用構造函數使得其爲公平鎖。如果爲true代表公平鎖Lock lock = new ReentrantLock(true);

綁定條件

這個就是的條件鎖。

在性能上,在jdk1.6之前的版本,使用ReentrantLock的性能要好於使用synchronized。而在jdk1.6開始,兩者性能均差不多。


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