[轉]Java執行緒詳解

sdvingo發表於2011-08-31

先從執行緒的建立說起.執行緒的建立一共有兩種形式:

--------------------------------------------------------------------------------

一種是繼承自Thread.Thread 類是一個具體的類,即不是抽象類,該類封裝了執行緒的行為。要建立一個執行緒,程式設計師必須建立一個從 Thread 類匯出的新類。程式設計師透過覆蓋 Thread run() 函式來完成有用的工作。使用者並不直接呼叫此函式;而是透過呼叫 Thread start() 函式,該函式再呼叫 run()

例如:

public class Test extends Thread{
public Test(){
}
public static void main(String args[]){
Test t1 = new Test();
Test t2 = new Test();
t1.start();
t2.start();
}
public void run(){
//do thread's things
}
}

--------------------------------------------------------------------------------


另一種是實現Runnable介面,此介面只有一個函式,run(),此函式必須由實現了此介面的類實現。


例如:

public class Test implements Runnable{
Thread thread1;
Thread thread2;
public Test(){
thread1 = new Thread(this,"1");
thread2 = new Thread(this,"2");
}
public static void main(String args[]){
Test t = new Test();
t.startThreads();
}
public void run(){
//do thread's things
}
public void startThreads(){
thread1.start();
thread2.start();
}
}

兩種建立方式看起來差別不大,但是弄不清楚的話,也許會將你的程式弄得一團糟。兩者區別有以下幾點:

1.當你想繼承某一其它類時,你只能用後一種方式.

2.第一種因為繼承自Thread,只建立了自身物件,但是在數量上,需要幾個執行緒,就得建立幾個自身物件;第二種只建立一個自身物件,卻建立幾個Thread物件.而兩種方法重大的區別就在於此,請你考慮:如果你在第一種裡建立數個自身物件並且start()後,你會發現好像synchronized不起作用了,已經加鎖的程式碼塊或者方法居然同時可以有幾個執行緒進去,而且同樣一個變數,居然可以有好幾個執行緒同時可以去更改它。(例如下面的程式碼)這是因為,在這個程式中,雖然你起了數個執行緒,可是你也建立了數個物件,而且,每個執行緒對應了每個物件也就是說,每個執行緒更改和佔有的物件都不一樣,所以就出現了同時有幾個執行緒進入一個方法的現象,其實,那也不是一個方法,而是不同物件的相同的方法。所以,這時候你要加鎖的話,只能將方法或者變數宣告為靜態,將static加上後,你就會發現,執行緒又能管住方法了,同時不可能有兩個執行緒進入同樣一個方法,那是因為,現在不是每個物件都擁有一個方法了,而是所有的物件共同擁有一個方法,這個方法就是靜態方法。

而你如果用第二種方法使用執行緒的話,就不會有上述的情況,因為此時,你只建立了一個自身物件,所以,自身物件的屬性和方法對於執行緒來說是共有的。

因此,建議最好用後一種方法來使用執行緒。

public class mainThread extends Thread{
int i=0;
public static void main(String args[]){
mainThread m1 = new mainThread();
mainThread m2 = new mainThread();
mainThread m3 = new mainThread();
mainThread m4 = new mainThread();
mainThread m5 = new mainThread();
mainThread m6 = new mainThread();
m1.start();
m2.start();
m3.start();
m4.start();
m5.start();
m6.start();
}
public synchronized void t1(){
i=++i;
try{
Thread.sleep(500);
}
catch(Exception e){}
//
每個執行緒都進入各自的t1()方法,分別列印各自的i

System.out.println(Thread.currentThread().getName()+" "+i);
}
public void run(){
synchronized(this){
while (true) {
t1();
}
}
}
}


--------------------------------------------------------------------------------

下面我們來講synchronized4種用法吧:

1.方法宣告時使用,放在範圍運算子(public)之後,返回型別宣告(void)之前.即一次只能有一個執行緒進入該方法,其他執行緒要想在此時呼叫該方法,只能排隊等候,當前執行緒(就是在synchronized方法內部的執行緒)執行完該方法後,別的執行緒才能進入.

例如:

public synchronized void synMethod() {
//
方法體
}

2.對某一程式碼塊使用,synchronized後跟括號,括號裡是變數,這樣,一次只有一個執行緒進入該程式碼塊.例如:

public int synMethod(int a1){
synchronized(a1) {
//
一次只能有一個執行緒進入
}
}
3.synchronized
後面括號裡是一物件,此時,執行緒獲得的是物件鎖.例如:

public class MyThread implements Runnable {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt, "t1");
Thread t2 = new Thread(mt, "t2");
Thread t3 = new Thread(mt, "t3");
Thread t4 = new Thread(mt, "t4");
Thread t5 = new Thread(mt, "t5");
Thread t6 = new Thread(mt, "t6");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}

public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName());
}
}
}



對於3,如果執行緒進入,則得到物件鎖,那麼別的執行緒在該類所有物件上的任何操作都不能進行.在物件級使用鎖通常是一種比較粗糙的方法。為什麼要將整個物件都上鎖,而不允許其他執行緒短暫地使用物件中其他同步方法來訪問共享資源?如果一個物件擁有多個資源,就不需要只為了讓一個執行緒使用其中一部分資源,就將所有執行緒都鎖在外面。由於每個物件都有鎖,可以如下所示使用虛擬物件來上鎖:

class FineGrainLock {

MyMemberClass x, y;
Object xlock = new Object(), ylock = new Object();

public void foo() {
synchronized(xlock) {
//access x here
}

//do something here - but don't use shared resources

synchronized(ylock) {
//access y here }
}

public void bar() {
synchronized(this) {
//access both x and y here }
//do something here - but don't use shared resources
}
}

4.synchronized後面括號裡是類.例如:

class ArrayWithLockOrder{
private static long num_locks = 0;
private long lock_order;
private int[] arr;

public ArrayWithLockOrder(int[] a)
{
arr = a;
synchronized(ArrayWithLockOrder.class) {//-----------------------------------------
這裡

num_locks++; //
鎖數加 1 lock_order = num_locks; // 為此物件例項設定唯一的 lock_order
}
}
public long lockOrder()
{
return lock_order;
}
public int[] array()
{
return arr;
}
}

class SomeClass implements Runnable
{
public int sumArrays(ArrayWithLockOrder a1,
ArrayWithLockOrder a2)
{
int value = 0;
ArrayWithLockOrder first = a1; //
保留陣列引用的一個

ArrayWithLockOrder last = a2; //
本地副本。
int size = a1.array().length;
if (size == a2.array().length)
{
if (a1.lockOrder() > a2.lockOrder()) //
確定並設定物件的鎖定
{ //
順序。
first = a2;
last = a1;
}
synchronized(first) { //
按正確的順序鎖定物件。
synchronized(last) {
int[] arr1 = a1.array();
int[] arr2 = a2.array();
for (int i=0; i value += arr1[i] + arr2[i];
}
}
}
return value;
}
public void run() {
//...
}
}

對於4,如果執行緒進入,則執行緒在該類中所有操作不能進行,包括靜態變數和靜態方法,實際上,對於含有靜態方法和靜態變數的程式碼塊的同步,我們通常用4來加鎖.

以上4種之間的關係:

鎖是和物件相關聯的,每個物件有一把鎖,為了執行synchronized語句,執行緒必須能夠獲得synchronized語句中表示式指定的物件的鎖,一個物件只有一把鎖,被一個執行緒獲得之後它就不再擁有這把鎖,執行緒在執行完synchronized語句後,將獲得鎖交還給物件。
在方法前面加上synchronized修飾符即可以將一個方法宣告為同步化方法。同步化方法在執行之前獲得一個鎖。如果這是一個類方法,那麼獲得的鎖是和宣告方法的類相關的Class類物件的鎖。如果這是一個例項方法,那麼此鎖是this物件的鎖。


--------------------------------------------------------------------------------


下面談一談一些常用的方法:

wait(),wait(long),notify(),notifyAll()等方法是當前類的例項方法,

wait()
是使持有物件鎖的執行緒釋放鎖
;
wait(long)
是使持有物件鎖的執行緒釋放鎖時間為long(毫秒),再次獲得鎖,wait()wait(0)等價
;
notify()
是喚醒一個正在等待該物件鎖的執行緒,如果等待的執行緒不止一個,那麼被喚醒的執行緒由jvm確定
;
notifyAll
是喚醒所有正在等待該物件鎖的執行緒
.
在這裡我也重申一下,我們應該優先使用notifyAll()方法,因為喚醒所有執行緒比喚醒一個執行緒更容易讓jvm找到最適合被喚醒的執行緒.

對於上述方法,只有在當前執行緒中才能使用,否則報執行時錯誤java.lang.IllegalMonitorStateException: current thread not owner.

--------------------------------------------------------------------------------


下面,我談一下synchronizedwait()notify()等的關係:

1.synchronized的地方不一定有wait,notify

2.wait,notify的地方必有synchronized.這是因為waitnotify不是屬於執行緒類,而是每一個物件都具有的方法,而且,這兩個方法都和物件鎖有關,有鎖的地方,必有synchronized

另外,請注意一點:如果要把notifywait方法放在一起用的話,必須先呼叫notify後呼叫wait,因為如果呼叫完wait,該執行緒就已經不是current thread了。如下例:


import java.lang.Runnable;
import java.lang.Thread;

public class DemoThread
implements Runnable {

public DemoThread() {
TestThread testthread1 = new TestThread(this, "1");
TestThread testthread2 = new TestThread(this, "2");

testthread2.start();
testthread1.start();

}

public static void main(String[] args) {
DemoThread demoThread1 = new DemoThread();

}

public void run() {

TestThread t = (TestThread) Thread.currentThread();
try {
if (!t.getName().equalsIgnoreCase("1")) {
synchronized (this) {
wait();
}
}
while (true) {

System.out.println("@time in thread" + t.getName() + "=" +
t.increaseTime());

if (t.getTime() % 10 == 0) {
synchronized (this) {
System.out.println("****************************************");
notify();
if (t.getTime() == 100)
break;
wait();
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}

}

class TestThread
extends Thread {
private int time = 0;
public TestThread(Runnable r, String name) {
super(r, name);
}

public int getTime() {
return time;
}

public int increaseTime() {
return++time;
}

}

下面我們用生產者/消費者這個例子來說明他們之間的關係:

public class test {
public static void main(String args[]) {
Semaphore s = new Semaphore(1);
Thread t1 = new Thread(s, "producer1");
Thread t2 = new Thread(s, "producer2");
Thread t3 = new Thread(s, "producer3");
Thread t4 = new Thread(s, "consumer1");
Thread t5 = new Thread(s, "consumer2");
Thread t6 = new Thread(s, "consumer3");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}

class Semaphore
implements Runnable {
private int count;
public Semaphore(int n) {
this.count = n;
}

public synchronized void acquire() {
while (count == 0) {
try {
wait();
}
catch (InterruptedException e) {
//keep trying
}
}
count--;
}

public synchronized void release() {
while (count == 10) {
try {
wait();
}
catch (InterruptedException e) {
//keep trying
}
}
count++;
notifyAll(); //alert a thread that's blocking on this semaphore
}

public void run() {
while (true) {
if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("consumer")) {
acquire();
}
else if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("producer")) {
release();
}
System.out.println(Thread.currentThread().getName() + " " + count);
}
}
}

生產者生產,消費者消費,一般沒有衝突,但當庫存為0,消費者要消費是不行的,但當庫存為上限(這裡是10),生產者也不能生產.請好好研讀上面的程式,你一定會比以前進步很多.

上面的程式碼說明了synchronizedwait,notify沒有絕對的關係,synchronized宣告的方法、程式碼塊中,你完全可以不用wait,notify等方法,但是,如果當執行緒對某一資源存在某種爭用的情況下,你必須適時得將執行緒放入等待或者喚醒.

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/443058/viewspace-1054618/,如需轉載,請註明出處,否則將追究法律責任。

相關文章