Java Thread

Java Threadjava

使用Java多線程編程很容易. Java線程老是實現接口java.lang.Runnable, 通常有兩種方法: 建立一個類實現接口Runnable, 創造該類的實例做爲參數傳給Thread構造函數, 創造Thread實例.程序員

package  tony.test.testJavaThread;

/**
 * @author Tony
 
*/

public   class  TestRunnable  implements  Runnable

    
int count = 0;
    
    
private synchronized void printHello()
    
{
        System.out.println(++count + ". Hello " + Thread.currentThread().getName());  
    }

    
    
public void run()
    
{
        printHello(); 
    }

    
    
public static void main(String[] args)
    
{
        TestRunnable tr = new TestRunnable();
        
for (int i=0; i<5; i++)
        
{
            
new Thread(tr, "Tony's Thread-" + (i+1)).start();
        }

    }

}

繼承java.lang.Thread, 創造該類對象. 這種方法通常要重寫(Override)run方法, 否則該線程就什麼也沒作就結束了.編程

package  tony.test.testJavaThread;

/**
 * @author Tony
 
*/

public   class  TestThread  extends  Thread
{
    
static int count = 0;
    
    
public TestThread(String name)
    
{
        
super(name);
    }

    
    
public void run()
    
{
        
synchronized(this.getClass())
        
{
            System.out.println(++count + ". Hello " + this.getName()); 
            
if (count == 2)
            
{
                System.out.println(this.getName() + " is to sleep for 4s"); 
                
try
                
{
                    Thread.sleep(4000);
                }

                
catch (InterruptedException e)
                
{
                    e.printStackTrace();
                }

            }

        }

    }

    
    
public static void main(String[] args)
    
{
        
for (int i=0; i<5; i++)
        
{
            
new TestThread("Tony's Thread-"+(i+1)).start();
        }

    }

}

二者能夠實現一樣的功能. 但我的比較喜歡前者. 因爲Java是單繼承, 因此後者不能再繼承其它類了. 而前者還有個好處就是能夠把線程間共享的數據做爲類的字段, 而後把該類實現Singleton, 只實例化一個對象, 做爲參數傳給Thread. 固然若是不想共享成員, 而對於每一個Thread提供不一樣的Runnable對象. 然後者要實現共享就要在繼承的類中聲明一堆static屬性.安全

Java Thread經過方法start啓動, 而實際運行的內容在方法run中. 不過簡單地調用run, 如thread.run(); 只是把run做爲普通的方法調用了一遍, 並無啓動新的線程. 當run中的內容運行完以後, 線程便自動結束了. 類Thread提供一個靜態方法sleep. 任什麼時候候調用該方法均可以把當前執行的線程暫停指定的時間.多線程

Java Thread同步與synchronizedide

先說明幾點:函數

1. 不管synchronized關鍵字加在方法上仍是對象上, 它取得的鎖都是對象的鎖 (JDK DOC中描述爲an object's monitor), 而不是指對方法或一段代碼加鎖. 加在方法上取得的鎖是該方法所屬對象的鎖, 即加在非static方法上時取得的是this的鎖, 加在static方法上時取得的是class的鎖. 這樣並行調用了一個類的多個對象加了synchronized的方法, 它們之間是沒有絲毫關係的.ui

2. 每一個對象只有一個鎖與之相關聯.而每一個線程能夠取得N個對象的鎖.this

3. 這個鎖其實是加在對象在堆中的地址上. 因此像基本類型等位於棧上的是不能加鎖的. 而對象引用自己也是在棧上, 因此也不會被加鎖. 這就是說像以下的代碼, 實際上兩個線程取得不是同一個鎖:spa

package  tony.test.testJavaThread;

/**
 * @author p465890
 
*/

public   class  TestSynchronized
{
    
public static void main(String[] args)
    
{
        Runnable rb = new Runnable()
        
{
            
// It is said that new Byte[0] is more effective than new Object()
            
// For the former needs two byte codes and the later needs three.
            
// I have not digged into it.
            
// Maybe the reason is as follows.
            
// "Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader."
            byte[] bt = new byte[0];
            
public void run()
            
{
                
synchronized(bt)
                
{
                    
for (int i=0; i<5; i++)
                    
{
                        
try
                        
{
                            Thread.sleep(50);
                        }

                        
catch (InterruptedException e)
                        
{
                            e.printStackTrace();
                        }

                        bt = new byte[0];
                        System.out.println(Thread.currentThread().getName());
                    }

                }

            }

        }
;
        
for (int i=0; i<5; i++)
        
{
            
try
            
{
                Thread.sleep(100);
            }

            
catch (InterruptedException e)
            
{
                e.printStackTrace();
            }

            
new Thread(rb).start();
        }

    }

}

4. 實現同步是要很大的系統開銷做爲代價的, 因此不須要同步就不要作同步。

Wait, notify, notifyAll

這三個函數都是Object的成員方法, 也就是說全部的對象都能調用這三個函數, 這與全部的對象上都有鎖是一致的, 並且這三個方法的也是對本身所屬對象鎖進行操做, 來影響須要該對象鎖的線程的行爲.

注意下面所說的new, run, schedule, wait, sleep, dead狀態並非官方的說法, 但這樣解釋能夠理解不少問題. 而且目前還沒碰到解釋不通的現象.

這樣說吧, 任何一個對象的鎖就比如通行證, synchronized就用來標誌某一塊程序只有用它指定的對象的通行證才能進入. 這種通行證每一個對象都有且只有一個, 而一個synchronized也能且只能指定一個對象(固然synchronized能夠嵌套, 但每個synchronized也只能指定一個對象), 多個synchronized能夠指定同一個對象, 由於對象的通行證只有一個, 因此這幾個synchronized塊中的東西就如同一塊同樣, 不能同時進入. 可是沒有用synchronized修飾的程序天然不須要通行證, 不管哪一個線程均可進入.

一個線程建立好了, 但沒啓動 (沒調用start), 此時它處於new狀態, 一個線程結束了就處於dead狀態. 這兩種狀態很簡單, 再也不多說.

對於線程來講, 要麼進入不需通行證的代碼(沒用synchronized修飾的代碼), 要麼千方百計取得通行證. 但線程不像人, 它是很守規矩的. 當一個線程想進入synchronized塊時, 若是該synchronized指定的對象的通行證沒被其餘線程使用, 它就拿走通行證進入該synchronized塊, 注意, 一量它出來, 它必定會把通行放回原處. 此時它處於run狀態. 但此時又有線程來了, 它也想進入該synchronized塊, 它只能等待在run狀態的線程歸還通行證, 它會一直這樣耐心等待. 若是此時又有其餘線程來了, 這樣就有了一個等待的隊列, 咱們稱這些線程處於schedule狀態, 這種狀態的線程一切都準備好了, 只等着有通行證就進入run狀態. 固然當通行證可用時, 只有一個線程可以獲得, 其餘仍然處於schedule狀態. 不過到底誰有幸獲得通行證, 那就由具體的JVM決定了, 程序員千萬別想着JVM會跟本身想法同樣. 我說過, 線程很守規矩. 實際上線程們還很友好. 一個處於run狀態的線程run了一會以後, 它可能想讓其餘線程先run一把, 因而它就調用它擁有的通行證的對象的wait方法, 告知本身要wait了, 此時通行證歸還, 而它本身則進入wait狀態. 這個線程能夠設定它將處於wait狀態的時間或者無限長(參數0時無限長, 負數會拋java.lang.IllegalArgumentException異常. 一旦時間到了, 就自動進入schedule狀態, 等待通行證. 在這個時間以內, 除非其餘線程喚醒它, 不然它將一直漠不關心了, 一直wait, 實際上說sleep更確切. 怎麼喚醒在下面會講.既然線程如此友好, 天然JVM也待它不薄, 一個線程wait後, 當它從新有機會進入schedule, 再進入run, 它裝從wait的下一個語句開始執行, 就好像它沒有wait過同樣. 當不少線程都wait以後, 就有了wait隊列, 而處於run的線程, 可能會想喚醒一個或一些wait的進程, 它能夠調用擁有的通行證的對象的notify方法(喚醒一個線程, 具體哪個, 也是JVM說了算)或者notifyAll方法(所有喚醒). 這裏須要說明的是, 被喚醒的是那些處於相應對象通行證wait隊列的線程, 其它無關的線程並不會被喚醒, 喚醒以後處於schedule狀態, 此時通行證仍然爲那個run狀態的線程全部, 最後到底誰能進入run狀態, 也是JVM決定的. 有時線程太好心了, 沒有某個對象的通行證, 卻硬要調用這個對象的wait, notify或notifyAll方法, 結果會拋出java.lang.IllegalMonitorStateException異常.

注意上面所講的, 各個通行證之間是獨立的, 好比, 一個線程調用一個對象的wait, 它歸還了這個對象的通行證, 但要是它還擁有其它對象的通行證, 它會仍然擁有, 固然這可能形成死鎖. 不過這應該是程序員的責任.

類Thread

難道全部線程都這麼公平, 固然不是. 類Thread提供了不少方法都針對某個線程進行操做, 它要麼不影響要麼影響指定線程所擁有的鎖. 也就是說這些方法與上面的不一樣, 是針對線程而不是針對鎖的.

一個線程處於wait時, 除了上面講的等到指定時間, notify, notifyAll以外, 它還能被其餘線程interrupt喚醒. interrupt()是Thread類的方法. 因此它做的是指名道姓的喚醒, 一個線程想喚醒另外一個線程, 它就必須直接叫那個線程的名字, 即調用那個線程的interrupt()方法. 這個處於wait的線程就會拋出java.lang.InterruptedException異常, consume異常以後, 該線程就會進入到schedule狀態, 而且若是以前其interrupt status(這個status與前面所提的run, schedule, wait狀態不要緊, 其實更該稱之爲屬性, 估計這就是Thread類的屬性, 只是JDK DOC 上這樣稱呼. 因此這裏用status以示區別), 這個interrupt status會被清除. 這裏要說明一下, 線程還有sleep狀態, 這種狀態行爲與wait基本同樣, 只是不能被nofity和notifyAll喚醒. 線程能夠調用Thread的靜態方法sleep, 使當前線程(本身)進入sleep狀態, 參數指定時間, 或Thread的成員方法join, 參數幾乎和調用wait同樣, 但它是使本身處於進入sleep狀態, 一直到被調用線程運行完了, 或者時間到了, 它才繼續運行.

package  tony.test.testJavaThread;

/**
 * @author Tony
 
*/

public   class  TestJoin  implements  Runnable
{
    
int count = 0;

    
public static void main(String[] args)
    
{
        TestJoin tj = new TestJoin();
        
for (int i=0; i<5; i++)
        
{
            
new Thread(tj, "hello"+i).start();
        }

    }


    
public void run()
    
{   
        
try
        
{
            Thread.sleep(1000);
        }

        
catch (InterruptedException e1)
        
{
            e1.printStackTrace();
        }

        
        
if (count == 0)
        
{
            count++;
            Thread thread1 = new Thread(this"helloSon");
            thread1.start();
            
try
            
{
                thread1.join(3000);
            }

            
catch (InterruptedException e)
            
{
                e.printStackTrace();
            }

        }

        System.out.println(Thread.currentThread().getName());
    }

}

只是Thread的這幾個方法顯然與擁有哪一個對象的通行證沒有關係. 實際上他們擁有的對象的通行證沒有任何改變, 不然是不安全的. 實際上像stop, suspend, resume等等deplicated的方法要麼調用時會釋放全部它擁有的鎖(通行證), 致使不安全, 要麼調用以後必定要某個方法來從新啓動, 容易形成死鎖. sleep狀態和wait同樣的是都能被interrupt喚醒, 喚醒的行爲如出一轍. 若是一個線程沒有處於wait和sleep狀態, 而且也沒有」 blocked in an I/O operation upon an interruptible channel」或」blocked in a Selector」, 它的interrupt status會被設置. Thread同時提供了兩個方法來查詢一個進程的interrupt status: 一種是靜態方法interrupted()查詢當前進程的interrupt status, 返回boolean值, 同時清除interrupt status; 另外一種是成員方法isInterrupted(), 但它不會清除interrupt status. 因此要想查詢當前線程interrupt status又不想清除之, 能夠這樣: Thread.currentThread.isInterrupted().

前面所說的一開始線程的interrupt status都爲false, 若是一開始interrupt status就被設爲true, 那麼該線程一調用wait, sleep或join就會拋出java.lang.InterruptedException異常, 而且interrupt status被清除. 實際上咱們若是把interrupt()的調用看到先設置interrupt status, 接下來的行爲就一致了, 而不論是先設interrupt status, 仍是先進爲wait或sleep狀態.

package  tony.test.testJavaThread;

/**
 * @author Tony
 
*/

public   class  TestInterrupt  implements  Runnable

    
int count = 0;
    
    
private synchronized void printHello()
    
{
        System.out.println(++count + ". Hello " + Thread.currentThread().getName());
        System.out.println(Thread.currentThread().getName()+""+Thread.currentThread().isInterrupted());
        
if (count == 1)
        
{
            
try
            
{
                wait();
            }

            
catch (InterruptedException e)
            
{
                System.out.println(Thread.currentThread().getName()+""+Thread.currentThread().isInterrupted());
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + " is back");
        }

        System.out.println(Thread.currentThread().getName()+""+Thread.currentThread().isInterrupted());
    }

    
    
public void run()
    
{
        printHello(); 
    }

    
    
public static void main(String[] args)
    
{
        TestInterrupt tr = new TestInterrupt();
        Thread[] threads = new Thread[5];
        
for (int i=0; i<5; i++)
        
{
            threads[i] = new Thread(tr, "Tony's Thread-" + (i+1));
            threads[i].start();
            threads[i].interrupt();
        }

        
        
try
        
{
            Thread.sleep(2000);
        }

        
catch (InterruptedException e)
        
{
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+""+Thread.interrupted());

        threads[0].interrupt();
    }

}

線程們之間友好還體如今Thread靜態方法yield(), 它所作的是讓本身暫停一下. 實際上就至關於把本身的狀態從run變到schedule, 從新競爭通行證(鎖), 最後到底誰會進入run, 或者它本身又進入了run, 由JVM決定.

線程能夠設置或查詢優先級, 高優先級進程優先. 線程還能設置或查詢是否成爲Daemon線程. Daemon線程和非Daemon線程的區別是JVM對它們的態度不一樣. 只要沒有非Daemon線程存在, JVM就退出, 就像調用了Sysem.exit()同樣, 而無論到底有沒有或有多少Daemon線程仍在運行.

類ThreadGroup

至於ThreadGroup, 它所作的就是把線程分組來管理, 好比, 調用ThreadGroup的interrupt方法, 至關於調用組內全部線程的interrupt方法. 組是樹裝結構的, 樹的根結點組的名稱是」system」. 若是不指定組名, 建立的Thread或ThreadGroup就是當前組的子結點. Java程序main方法所在的組名爲」main」, 是」system」的子結點, 調用ThreadGroup的list方法就能夠把從該組開始的樹狀結構比文本形式打印出來.

ThreadGroup和Thread的name均可以同樣.

package  tony.test.testJavaThread;

/**
 * @author p465890
 
*/

public   class  TestThreadGroup
{

    
public static void main(String[] args)
    
{
        ThreadGroup tg = new ThreadGroup("tg-hello");
        
new Thread(tg, new Runnable()
        
{
            
public void run()
            
{                
            }

        }
"th-world");
        ThreadGroup tg2 = new ThreadGroup("tg-hello");
        
        tg.getParent().getParent().list();
    }

}

ThreadGroup提供了一個處理非Catch異常的方法, 只要重載ThreadGroup中的uncaughtException方法, 屬於該組的Thread只要拋了非Catch異常, 就會調用此方法.

package  tony.test.testJavaThread;

/**
 * @Tony
 
*/

public   class  TestUncaughtException  extends  ThreadGroup
{
    
public TestUncaughtException(String name)
    
{
        
super(name);
    }

    
    
public void uncaughtException(Thread t, Throwable e)
    
{
        System.out.println(e.getClass().getName() + " occurs in thread " + t.getName());
        e.printStackTrace();
    }

    
    
public static void main(String[] args)
    
{
        
new Thread(new TestUncaughtException("testUncaughtException"), new Runnable()
            
{
                
public void run()
                
{
                    System.out.println("Test uncaughtException: 1/0");
                    
int i = 1 / 0;
                    System.out.println("Test uncaughtException ends");
                }

            }
).start();
    }

}

對於JDK1.5以止版本, Thread自己就提供了setUncaughtExceptionHandler, 在線程粒度處理非Catch異常. JDK DOC中描述以下:

 「Uncaught exception handling is controlled first by the thread, then by the thread's ThreadGroup object and finally by the default uncaught exception handler. If the thread does not have an explicit uncaught exception handler set, and the thread's thread group (including parent thread groups) does not specialize its uncaughtException method, then the default handler's uncaughtException method will be invoked. 「

package  tony.testJavaThread;

public   class  TestUncatchedExceptionHandler
{
    
public static void main(String[] args)
    
{
        Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
            
{
                
public void uncaughtException(Thread t, Throwable e)  
                
{
                    System.out.println(e.getClass().getName() + " occurs in thread " + t.getName());
                    e.printStackTrace();
                }

            }
);
        
int i = 1 / 0;
    }

}

Thread還提供方法getContextClassLoader, 獲得該線程的ClassLoader對象.

原文:http://blog.csdn.net/tonywjd/archive/2007/01/15/1483958.aspx

相關文章
相關標籤/搜索