<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    walterwing  
    日歷
    <2009年12月>
    293012345
    6789101112
    13141516171819
    20212223242526
    272829303112
    3456789
    統計
    • 隨筆 - 12
    • 文章 - 1
    • 評論 - 7
    • 引用 - 0

    導航

    常用鏈接

    留言簿(1)

    隨筆分類

    隨筆檔案

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

     
    看了一陣子java線程方面的知識,《thinking in java 3rd》,《effective java 2nd》,感覺還是霧里看花,難以得其精髓。

    多線程編程本來就是一門很玄奧的學問,不是看一些基礎的語法知識就能真正掌握的。在實踐中去揣摩,我想才是最好的方法。奈何我現在沒有這樣的條件,離論文開題的時間不遠了,我還沒有摸到頭緒,真不知道是該堅持還是放棄。

    扯遠了,還是回到線程來吧,雖然不得要領,但還是要把一些基礎的東西總結一下,一旦以后需要用到的時候,也可以方便地回顧。



    1. 線程的創建

    java中創建一個線程有兩種方式:

    1.1. 擴展Thread類,并重載run()方法

    public class ThreadName extends Thread {
        
    public void run() {
            
    // do something here
        }

    }


    1.2. 實現runnable接口,并調用Thread提供的構造函數

    public class ThreadName implements Runnable {

        
    public void run() {
            
    // TODO Auto-generated method stub

        }

        
        
    public void main(String args[]) {
            Thread thread 
    = new Thread(new ThreadName());
            thread.start();
        }


    }

     

    這兩種方法各有利弊。簡單來說,如果你確定當前的類就是作為一個單純的線程來實現,不需要再繼承其他任何類的時候,那么最好就用第一種方式,因為簡單,而且可以直接就獲得Thread所提供的各種方法,在類內部使用;反之,如果你需要繼承其他類,或者將來可能會有這種需要,那么就用第二種方法。

    第二種方法需要注意的地方是,當你實現了Runnable接口后,你僅僅是實現了該接口而已,你現在所有的只是一個run()方法,即使你生成一個對象來調用run()方法,和普通的方法調用也沒什么兩樣,并不會創建一個新的線程。只有當你用Thread構造函數創建一個對象之后,這才是新創建了一個線程。

    當你使用第二種方法的時候,可能你也想在類內部調用Thread所提供的一些方法,這時可以用Thread.currentThread()來獲得當前線程的引用。


    2. 線程的運行

    當你獲得一個線程實例thread后,使他開始運行的唯一方法就是thread.start(),由java虛擬機調用該線程的run方法。

    注意不是調用run()方法來啟動線程。當你調用run()方法時,如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作并返回。

    另外要注意,多次啟動一個線程是非法的。特別是當線程已經結束執行后,不能再重新啟動。


    3. 結束線程運行

    就我目前所知,有兩種結束線程的方法(我是指通過直接操作線程對象的合法方法)。

    3.1. 在線程內部設置一個標記為volatile的標志位

    public class StopThread extends Thread {
        
    public volatile boolean stop = false;
        
        
    private static int i = 0;
        
        
    public void run() {
            
    while(!stop) {
                i
    ++;
            }

            
            System.out.println(i);
        }


        
    /**
         * 
    @param args
         
    */

        
    public static void main(String[] args) {
            StopThread thread 
    = new StopThread();
            thread.start();
            
            
    try {
                sleep(
    2000);
            }
     catch (InterruptedException e) {
                
    // TODO Auto-generated catch block
                e.printStackTrace();
            }

            
            thread.stop 
    = true;
        }


    }


    關于volatile的說明可能需要先了解JAVA的內存模型,簡單點說就是JVM有個主存,各個線程再有各自的工作內存,兩個存放的地方帶來的問題就是不一致。volatile就是為了解決這個不一致出現的。

    使用volatile會每次修改后及時的將工作內存的內容同步回主存。這樣,線程所看到的就是最新的值,而不是被緩存的值。

    注,這里還牽涉到“原子操作”的概念。所謂原子操作,大體上就是指操作只由一個指令即可完成,不需要上下文切換。在java中,對除long和double之外的基本類型進行簡單的賦值或者返回值操作的時候,才是原子操作。然而,只要給long或double加上volatile,就和其他基本類型一樣了。但自增操作并不是原子操作,它牽涉到一次讀一次寫!

    正因為對boolean的操作是原子操作,我們不用擔心多個線程同時對boolean值進行修改而導致不一致的情況,所以在修改、讀取boolean值的時候不需要加synchronized關鍵字。

    總結一下volatile關鍵字的兩個作用:
    1) 保證聲明為volatile的64位變量的加載或存儲是個基本的單元操作
    2) 保證在多處理器計算機中,即使是處理高速緩存中的數據,易失性變量的加載和存儲也能夠正確地進行


    3.2. 調用interrrupt()方法

    有的時候,線程可能會阻塞,比如在等待輸入的時候,并且他也不能輪詢結束標志。這個時候,可以用Thread.interrupt()方法來跳出阻塞代碼。

    public class Blocked extends Thread {
        
    public Blocked() {
            System.out.println(
    "Starting");
        }

        
        
    public void run() {
            
    try {
                
    synchronized(this{
                    wait();
                }

            }
     catch(InterruptedException e) {
                System.out.println(
    "Interrupted");
            }

            
            System.out.println(
    "Exiting run()");
        }

        
        
        
    public static void main(String args[]) {
            Blocked thread 
    = new Blocked();
            thread.start();
            
            
    try {
                sleep(
    2000);
            }
     catch (InterruptedException e) {
                
    // TODO Auto-generated catch block
                e.printStackTrace();
            }

            
            thread.interrupt();
                thread = null;
        }

    }

    注意,我們在用interrupt終止線程后,最好再將該線程賦為null,這樣垃圾回收器就可以回收該線程了。
    另,使用interrupt()并不需要獲得對象鎖 - 這與wait()、notify()等不同

    上面例子中有一個比較tricky的地方:當我們使用interrupt()方法中斷線程的運行時,線程將拋出InterruptedException,但在拋出exception的同時,他的中斷狀態將被清除,所以如果我們在catch(InterrruptedException e) { }里調用isInterrupted(),返回的結果將會是false。

    Thread類提供了兩種方法來判斷一共線程是否處于中斷狀態:

    interrupted(): 靜態方法,用于檢查當前進程是否已經被中斷,同時清除線程的中斷狀態
    isInterrupted(): 實例方法,用于檢查任何一個線程是否已經被中斷,不會清除中斷狀態

    在編程實踐中盡量不要catch InterruptedException,因為這會清除中斷標記。如果要catch,那么在catch塊里重新設置中斷標志:Thread.currentThread().interrupt(); 或者干脆不catch,聲明throws InterruptedException


    當你使用Timer類調度線程的時候,可以使用Timer類提供的cancel()方法來終止線程的運行。Timer類還是比較好用的,具體參見API doc。


    4. wait(), notify(), notifyAll()

    這三個方法是線程同步機制的基礎,但這三種方法已經被Joshua Bloch視為“low-level”的,“匯編級別”的代碼,應該盡量被JDK 1.5以來提供的高層次框架類取代。這正是java讓人又愛又恨的地方 - 它總是提供各種方便易用的API供使用者調用,幫助編程人員提高效率,避免錯誤,但與此同時,它也在無形之間將底層機制與使用隔離,使相當一批編程者“淪為”API的“純”調用者,只懂得用一堆API來堆起一個程序。很不幸,我就是其中之一。但我總算還保留著一點求知的欲望。

    使用wait(),總是要最先想到,一定要用while循環來判斷執行條件是否滿足:

    synchronized(obj) {
        
    while(conditionIsNotMet) {
            wait();
        }


         
    // Perform action approriate to condition
    }

    這樣就可以保證在跳出等待循環之前條件將被滿足,如果你被不相干的條件所通知(比如notifyAll()),或者在你完全退出循環之前條件已經改變,你被確保可以回來繼續等待。

    有兩個wait方法帶有超時參數:
    void wait(long millis);
    void wait(long millis, int nanos);
    但wait方法返回時,無法確定返回的原因是因為超時還是得到了通知。有兩種方式可以解決這個問題:

    1)
    long before = System.currentTimeMillis();
    wait(delay);
    long after = System.currentTimeMillis();
    if(after - before > delay)
         
    // timeout

    2)可以讓負責通知的線程設置一個標志

    對于notify(), notifyAll(),Joshua Bloch的建議是盡量使用notifyAll(),以避免出現某些進程永遠沉睡的現象。

    5. synchronized

    synchronized是將對象中所有加鎖的方法(or代碼塊)鎖定。由于同一時間只能有一個線程擁有對象的鎖,這也就保證了互斥。

    有兩種加鎖形式:

    5.1. 給代碼塊加鎖:

    synchronized(obj) {
         
    // some codes here
    }

    5.2. 給方法加鎖:

    public synchronized void method() {
         
    // some codes here
    }

    注意,java中不允許在重載(重寫?)的時候改變簽名,但sychronized關鍵字并不屬于簽名,因此,你可以繼承一個類,然后重載(重寫?)這個幾類的方法,加上sychronized關鍵字來保證互斥

    如果某個線程擁有一個對象的鎖,并且它調用了同一個對象上的另一個synchronized方法,那么該線程將自動被賦予對該方法的訪問權。只有當該線程退出上一個synchronized方法時,它才會釋放該鎖。

    這是因為每個對象都有一個鎖計數器,用于計算鎖的所有者調用了多少次synchronized方法。當鎖計數器的值達到0時,該線程便放棄對該鎖的所有權。

    對于鎖,需要明確下面的關系:

    你可以擁有同一個類的不同對象,每個對象被不同的線程鎖定。這些線程甚至可以執行同一個synchronized方法。因為我們鎖定的是對象,而不是方法。當然,在某個時間點上,一個對象的鎖只能被一個線程擁有。不過,一個線程可以同時擁有多個對象的鎖,只需要在執行一個對象上的synchronized方法的同時,又執行另一個對象的synchronized方法即可。

    synchronized一個較為特殊的應用是給靜態方法加鎖:

    public class Singleton
    {
        
    private static Singleton instance;

        
    private Singleton( }

        
    public static synchronized Singleton getInstance() {
            
    if(instance == null)
                instance 
    = ?31span style="color: #0000ff">few
     Singleton();
            
    return instance;
        }

    }

    當一個線程調用Synchronized方法時,它便會獲取對象的鎖。但是該方法是靜態方法,當調用Singleton.getInstance()時,哪個對象負責執行線程鎖的操作呢?

    調用一個靜態方法將會鎖定類對象(比如Singleton.class)。因此,如果一個線程調用一個類的靜態synchronized方法,那么該類的所有靜態synchronized方法均會被鎖定,直到第一個調用返回為止。



    以上介紹了java線程中語法上的一些基礎的東西,下面要介紹的同樣也是基礎,但同上面而言還是有些差異,還是分開一段來介紹的好。

    1. 一些方法

    sleep():
    sleep()方法能迫使線程休眠指定長的時間。在調用sleep()方法的時候,必須把它放在try塊中,因為在休眠時間到期之前有可能被打斷。如果某人持有對此線程的引用,并且在此線程上調用了interrupt()方法,就會發生這種情況。

    daemon線程:
    必須在線程啟動之前調用setDaemon()方法,才能把它設置為后臺線程。一個后臺線程所創建的任何線程都將被自動設置成后臺線程

    join():
    一個線程可以在其他線程之上調用join()方法,其效果是等待一段時間直到第二個線程結束才繼續執行。如果某個線程在另一個線程t上調用t.join(),此線程將被掛起,直到目標線程t結束才恢復(即t.isAlive()返回為false)
    你也可以在調用join()時帶上一個超時參數(單位可以是毫秒或者毫秒+納秒),這樣如果目標線程在這段時間到期還沒結束的話,join()方法總能返回。
    對join()方法的調用可以被中斷,做法是在調用線程上使用interrupt()方法,這時需要用到try-catch

    isAlive():
    如果該線程是可運行線程或被中斷的線程,那么該方法返回true;如果該線程仍然是個新線程或尚未成為可運行線程,或者該線程是個死線程,那么該方法返回false
    注:無法確定一個“活”線程究竟是處于可運行狀態還是被中斷狀態,也無法確定一個運行線程釋放正處在運行之中。另外,你也無法對尚未成為可運行的線程與已經死掉的線程進行區分。


    2. 線程的四種狀態:創建、就緒、死亡、阻塞。

    線程進入阻塞狀態可能有如下四種原因:

    2.1. 通過調用sleep()使線程進入休眠狀態。在這種情況下,線程在指定時間內不會運行

    2.2. 通過調用wait()使線程掛起,直到線程得到了notify()或notifyAll()消息,線程才會進入就緒狀態

    2.3. 線程在等待輸入/輸出操作的完成

    2.4. 線程試圖在某個對象上調用其同步控制方法,但是對象鎖不可用



    3. 只有當下列四個條件同時滿足時,才會發生死鎖:

    3.1. 互斥條件:線程使用的資源中至少又一個是不能共享的

    3.2. 至少有一個進程持有一個資源,并且他在等待獲取一個當前被別的進程持有的資源。

    3.3. 資源不能被進程搶占。所有的進程必須把資源釋放作為普通事件。

    3.4. 必須有循環等待,即,一個線程等待其他線程持有的資源,后者又在等待另一個進程持有的資源,這樣一直下去,直到又一個進程在等待第一個進程持有的資源,使得大家都被鎖住。

    要發生死鎖,必須這四個條件同時滿足,所以,只要破壞其中任意一個,就可以打破死鎖。其中第四個條件是最容易被打破的。


    4. 線程的優先級

    JVM將線程的優先級映射為主機平臺的優先級等級。

    每當主機平臺使用的優先級低于Java平臺時,某個線程的運行就可能被另一個優先級明顯低得多的線程線程搶先。這意味著你不能依靠多線程程序中的優先級等級

    另外,調用yield方法,只會讓當前線程暫時放棄運行,而主機則始終準備對放棄運行的線程實施重新啟動。如果當前線程優先級較高,則可能主機一直重啟該線程,而其他低優先級線程將得不到運行。為此,yield也靠不住,sleep可能是更好的方式。

    5. java中對以“管道”形式對線程的輸入/輸出提供了支持

    PipedWriter類允許線程向管道寫;PipedReader類允許不同線程從一個管道中讀取。或是采用PipedInputStream和PipedOutputStream提供字節流支持

    使用管道的主要原因是為了使每個線程始終能保持簡單。可以將多個線程相互連接起來,而不必擔心線程的同步問題。

    但要注意,管道式數據流只適用于線程在低層次上的通信,在其他情況下,可以使用隊列。



    下面是對《effective java 2nd》中Concurrency一章的總結。感覺這一章并不如我所想象的那樣,對java的線程機制有一個全面透徹的解說,反而是花了很大力氣宣傳一本書 - 《Java Concurrency in Practice》。好吧,想在一章的內容里對java線程的認識達到某種高度,怎么想也是不太現實的。但這本書究竟如何,我還沒看過,不作評論,但我想肯定是很適合正在用java線程做項目的人的。對我而言,重要的不是學會怎么用java代碼來寫出多線程程序,而是搞清線程內部的機制。

    下面對Concurrency一章的重點知識(我認為重要的)進行一下總結:

     

    Item 66: Synchronize access to shared mutable data

    • when multiple threads share mutable data, each thread that reads or writes the data must perform synchronization. (讀寫都要加鎖,不能只加一個)
    • If you need only inter-thread communication, and not mutual exclusion, the volatile modifier is an acceptable form of synchronization, but it can be tricky to use correctly.


    Item 67: Avoid excessive synchronization

    • Inside a synchronized region, do not invoke a method that is designed to be overridden, or one provided by a client in the form of a function object
    • As a rule, you should do as little work as possible inside synchronized regions.
    • In summary, to avoid deadlock and data corruption, never call an alien method from within a synchronized region. More generally, try to limit the amount of work that you do from within synchronized regions.


    Item 68: Prefer executors and tasks to threads

    • Executor Framework - refer to Java Concurrency in Practice


    Item 69: Prefer concurrency utilities to wait and notify

    • Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead.
    • The higher-level utilities in java.util.concurrent fall into three categories: the Executor Framework; concurrent collections; and synchronizers.
    • Synchronizers are objects that enable threads to wait for one another, allowing them to coordinate their activities. The most commonly used synchronizers are CountDownLatch and Semaphore. Less commonly used are CyclicBarrier and Exchanger.
    • For interval timing, always use System.nanoTime in preference to System.currentTimeMillis.
    • Always use the wait loop idiom to invoke the wait method; never invoke it outside of a loop.
    • The notifyAll method should generally be used in preference to notify. If notify is used, great care must be taken to ensure liveness.


    Item 70: Document thread safety

    • Conditionallythread-safe classes must document which method invocation sequences require external synchronization, and which lock to acquire when executing these sequences.
    • If you write an unconditionally thread-safe class, consider using a private lock object in place of synchronized methods. This protects you against synchronization interference by clients and subclasses and gives you the flexibility to adopt a more sophisticated approach to concurrency control in a later release.


    Item 71: Use lazy initialization judiciously

    • In summary, you should initialize most fields normally, not lazily. If you must initialize a field lazily in order to achieve your performance goals, or to break a harmful initialization circularity, then use the appropriate lazy initialization technique.
    • For instance fields, it is the double-check idiom;
      For static fields, the lazy initialization holder class idiom;
      For instance fields that can tolerate repeated initialization, you may also consider the single-check idiom.


    Item 72: Don’t depend on the thread scheduler

    • When many threads are runnable, the thread scheduler determines which ones get to run, and for how long.
    • In summary, do not depend on the thread scheduler for the correctness of your program. The resulting program will be neither robust nor portable. As a corollary, do not rely on Thread.yield or thread priorities.
    • Thread priorities may be used sparingly to improve the quality of service of an already working program, but they should never be used to “fix” a program that barely works.


    Item 73: Avoid thread groups

    • Thread groups are best viewed as an unsuccessful experiment, and you should simply ignore their existence.

    我個人最喜歡第73條。。。
     

    posted on 2008-07-16 11:32 This is Wing 閱讀(3358) 評論(1)  編輯  收藏 所屬分類: Java基礎
    評論:
     
    Copyright © This is Wing Powered by: 博客園 模板提供:滬江博客
    主站蜘蛛池模板: 污视频网站免费观看| 亚洲欧洲自拍拍偷午夜色| 亚洲人成影院午夜网站| 中文字幕视频在线免费观看| 日本二区免费一片黄2019| 亚洲人成网www| 3344在线看片免费| 久久久久一级精品亚洲国产成人综合AV区 | 免费人成网站永久| 免费观看大片毛片| 亚洲精品国产综合久久久久紧 | 亚洲色精品vr一区二区三区 | 久久亚洲日韩看片无码| 久久久久久AV无码免费网站| 亚洲国产成人精品久久久国产成人一区二区三区综 | 亚洲youwu永久无码精品| 国产精品69白浆在线观看免费| 亚洲永久中文字幕在线| h视频在线观看免费完整版| 精品无码一区二区三区亚洲桃色| 人妻在线日韩免费视频| 亚洲AV无码一区二区乱子伦| 精品国产一区二区三区免费| 亚洲人成网址在线观看| 4虎永免费最新永久免费地址| 国产.亚洲.欧洲在线| 国产不卡免费视频| 成人av片无码免费天天看| 亚洲天天在线日亚洲洲精| 成人免费一区二区无码视频| 久久精品亚洲日本波多野结衣| gogo全球高清大胆亚洲| 两性色午夜视频免费播放| 亚洲日本香蕉视频| 亚洲成A人片在线观看无码3D | 最新国产AV无码专区亚洲| 4455永久在线观免费看| 青草青草视频2免费观看| 日韩va亚洲va欧洲va国产| 曰批视频免费30分钟成人| 国产99视频精品免费视频76|