程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> JAVA專題技術綜述之線程篇

JAVA專題技術綜述之線程篇

編輯:JAVA編程入門知識
編寫具有多線程能力的程序經常會用到的方法有:

run(),start(),wait(),notify(),notifyAll(),sleep(),yield(),join()

還有一個重要的關鍵字:synchronized

本文將對以上內容進行講解。

一:run()和start()

示例1:

public class ThreadTest extends Thread
{
public void run()
{
for(int i=0;i <10;i++)
{
System.out.print(" " + i);
}
}
public static void main(String[] args)
{
new ThreadTest().start();
new ThreadTest().start();
}
}

這是個簡單的多線程程序。run()和start()是大家都很熟悉的兩個方法。把希望並行處理的代碼都放在run()中;stat()用於自動調用run(),這是JAVA的內在機制規定的。並且run()的訪問控制符必須是public,返回值必須是void(這種說法不准確,run()沒有返回值),run()不帶參數。

這些規定想必大家都早已知道了,但你是否清楚為什麼run方法必須聲明成這樣的形式?這涉及到JAVA的方法覆蓋和重載的規定。這些內容很重要,請讀者參考相關資料。

二:關鍵字synchronized

有了synchronized關鍵字,多線程程序的運行結果將變得可以控制。synchronized關鍵字用於保護共享數據。請大家注意"共享數據",你一定要分清哪些數據是共享數據,JAVA是面向對象的程序設計語言,所以初學者在編寫多線程程序時,容易分不清哪些數據是共享數據。請看下面的例子:

示例2:

public class ThreadTest implements Runnable
{

public synchronized void run()
{
for(int i=0;i <10;i++)
{
System.out.print(" " + i);
}
}
public static void main(String[] args)
{
Runnable r1 = new ThreadTest();
Runnable r2 = new ThreadTest();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}

在這個程序中,run()被加上了synchronized關鍵字。在main方法中創建了兩個線程。你可能會認為此程序的運行結果一定為:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。但你錯了!這個程序中synchronized關鍵字保護的不是共享數據(其實在這個程序中synchronized關鍵字沒有起到任何作用,此程序的運行結果是不可預先確定的)。這個程序中的t1,t2是兩個對象(r1,r2)的線程。JAVA是面向對象的程序設計語言,不同的對象的數據是不同的,r1,r2有各自的run()方法,而synchronized使同一個對象的多個線程,在某個時刻只有其中的一個線程可以訪問這個對象的synchronized數據。每個對象都有一個"鎖標志",當這個對象的一個線程訪問這個對象的某個synchronized數據時,這個對象的所有被synchronized修飾的數據將被上鎖(因為"鎖標志"被當前線程拿走了),只有當前線程訪問完它要訪問的synchronized數據時,當前線程才會釋放"鎖標志",這樣同一個對象的其它線程才有機會訪問synchronized數據。

示例3:

public class ThreadTest implements Runnable
{
public synchronized void run()
{
for(int i=0;i <10;i++)
{
System.out.print(" " + i);
}
}
public static void main(String[] args)
{
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();

t2.start();
}
}

如果你運行1000次這個程序,它的輸出結果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因為這裡的synchronized保護的是共享數據。t1,t2是同一個對象(r)的兩個線程,當其中的一個線程(例如:t1)開始執行run()方法時,由於run()受synchronized保護,所以同一個對象的其他線程(t2)無法訪問synchronized方法(run方法)。只有當t1執行完後t2才有機會執行。

示例4:

public class ThreadTest implements Runnable
{
public void run()
{
synchronized(this)
{
for(int i=0;i <10;i++)
{
System.out.print(" " + i);
}
}
}
public static void main(String[] args)
{
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}

這個程序與示例3的運行結果一樣。在可能的情況下,應該把保護范圍縮到最小,可以用示例4的形式,this代表"這個對象"。沒有必要把整個run()保護起來,run()中的代碼只有一個for循環,所以只要保護for循環就可以了。

示例5:

public class ThreadTest implements Runnable
{
public void run()
{
for(int k=0;k <5;k++)
{
System.out.println(Thread.currentThread().getName()
+ " : for loop : " + k);

}
synchronized(this)
{
for(int k=0;k <5;k++)
{
System.out.println(Thread.currentThread().getName()
+ " : synchronized for loop : " + k);
}
}
}
public static void main(String[] args)
{
Runnable r = new ThreadTest();
Thread t1 = new Thread(r,"t1_name");
Thread t2 = new Thread(r,"t2_name");
t1.start();
t2.start();
}
}
運行結果: t1_name : for loop : 0
t1_name : for loop : 1
t1_name : for loop : 2
t2_name : for loop : 0
t1_name : for loop : 3
t2_name : for loop : 1
t1_name : for loop : 4
t2_name : for loop : 2
t1_name : synchronized for loop : 0
t2_name : for loop : 3
t1_name : synchronized for loop : 1
t2_name : for loop : 4
t1_name : synchronized for loop : 2
t1_name : synchronized for loop : 3
t1_name : synchronized for loop : 4
t2_name : synchronized for loop : 0
t2_name : synchronized for loop : 1
t2_name : synchronized for loop : 2
t2_name : synchronized for loop : 3
t2_name : synchronized for loop : 4

第一個for循環沒有受synchronized保護。對於第一個for循環,t1,t2可以同時訪問。運行結果表明t1執行到了k=2時,t2開始執行了。t1首先執行完了第一個for循環,此時還沒有執行完第一個for循環(t2剛執行到k=2)。t1開始執行第二個for循環,當t1的第二個for循環執行到k=1時,t2的第一個for循環執行完了。t2想開始執行第二個for循環,但由於t1首先執行了第二個for循環,這個對象的鎖標志自然在t1手中(synchronized方法的執行權也就落到了t1手中),在t1沒執行完第二個for循環的時候,它是不會釋放鎖標志的。所以t2必須等到t1執行完第二個for循環後,它才可以執行第二個for循環。
三:sleep()

示例6:

public class ThreadTest implements Runnable
{
public void run()
{
for(int k=0;k <5;k++)
{
if(k == 2)
{
try
{
Thread.currentThread().sleep(5000);
}
catch(Exception e)
{}
}
System.out.print(" " + k);
}
}
public static void main(String[] args)
{
Runnable r = new ThreadTest();
Thread t = new Thread(r);
t.start();
}
}

sleep方法會使當前的線程暫停執行一定時間(給其它線程運行機會)。讀者可以運行示例6,看看結果就明白了。sleep方法會拋出異常,必須提供捕獲代碼。

示例7:

public class ThreadTest implements Runnable
{
public void run()
{

for(int k=0;k <5;k++)
{
if(k == 2)
{
try
{
Thread.currentThread().sleep(5000);
}
catch(Exception e)
{}
}
System.out.println(Thread.currentThread().getName()
+ " : " + k);
}
}
public static void main(String[] args)
{
Runnable r = new ThreadTest();
Thread t1 = new Thread(r,"t1_name");
Thread t2 = new Thread(r,"t2_name");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}

t1被設置了最高的優先級,t2被設置了最低的優先級。t1不執行完,t2就沒有機會執行。但由於t1在執行的中途休息了5秒中,這使得t2就有機會執行了。讀者可以運行這個程序試試看。

示例8:

public class ThreadTest implements Runnable
{
public synchronized void run()
{
for(int k=0;k <5;k++)
{
if(k == 2)
{
try
{
Thread.currentThread().sleep(5000);
}
catch(Exception e)
{}

}
System.out.println(Thread.currentThread().getName()
+ " : " + k);
}
}
public static void main(String[] args)
{
Runnable r = new ThreadTest();
Thread t1 = new Thread(r,"t1_name");
Thread t2 = new Thread(r,"t2_name");
t1.start();
t2.start();
}
}

請讀者首先運行示例8程序,從運行結果上看:一個線程在sleep的時候,並不會釋放這個對象的鎖標志。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved