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

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

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

    letter Y A N. G Brass Letter F a n-spo D Pewter Uppercase Letter I N G
    隨筆 - 4, 文章 - 10, 評論 - 2, 引用 - 0
    數據加載中……

    ThreadLocal與synchronize

    Java良好的支持多線程。使用java,我們可以很輕松的編程一個多線程程序。但是使用多線程可能會引起并發訪問的問題。synchronized和ThreadLocal都是用來解決多線程并發訪問的問題。大家可能對synchronized較為熟悉,而對ThreadLocal就要陌生得多了。 
    并發問題。當一個對象被兩個線程同時訪問時,可能有一個線程會得到不可預期的結果。 

    一個簡單的java類Studnet 

     1public class Student {  
     2  private int age=0;  
     3    
     4  public int getAge() {  
     5      return this.age;  
     6        
     7  }
      
     8    
     9  public void setAge(int age) {  
    10      this.age = age;  
    11  }
      
    12}
      
    一個多線程類ThreadDemo. 
    這個類有一個Student的私有變量,在run方法中,它隨機產生一個整數。然后設置到student變量中,從student中讀取設置后的值。然后睡眠5秒鐘,最后再次讀student的age值。 

     1public class ThreadDemo implements Runnable{  
     2  Student student = new Student();  
     3  public static void main(String[] agrs) {  
     4     ThreadDemo td = new ThreadDemo();  
     5     Thread t1 = new Thread(td,"a");  
     6     Thread t2 = new Thread(td,"b");  
     7    t1.start();  
     8    t2.start();  
     9  
    10  }
      
    11/* (non-Javadoc) 
    12 * @see java.lang.Runnable#run() 
    13 */
      
    14 public void run() {  
    15     accessStudent();  
    16 }
      
    17   
    18 public void accessStudent() {  
    19        String currentThreadName = Thread.currentThread().getName();  
    20        System.out.println(currentThreadName+" is running!");  
    21       // System.out.println("first  read age is:"+this.student.getAge());  
    22        Random random = new Random();  
    23        int age = random.nextInt(100);  
    24        System.out.println("thread "+currentThreadName +" set age to:"+age);  
    25         
    26        this.student.setAge(age);  
    27        System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());  
    28        try {  
    29        Thread.sleep(5000);  
    30        }
      
    31        catch(InterruptedException ex) {  
    32            ex.printStackTrace();  
    33        }
      
    34        System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());  
    35           
    36 }
      
    37    
    38}
      
    運行這個程序,屏幕輸出如下: 
    a is running! 
    b is running! 
    thread b set age to:33 
    thread b first  read age is:33 
    thread a set age to:81 
    thread a first  read age is:81 
    thread b second read age is:81 
    thread a second read age is:81 

    需要注意的是,線程a在同一個方法中,第一次讀取student的age值與第二次讀取值不一致。這就是出現了并發問題。 

    synchronized 
    上面的例子,我們模似了一個并發問題。Java提供了同步機制來解決并發問題。synchonzied關鍵字可以用來同步變量,方法,甚至同步一個代碼塊。 
    使用了同步后,一個線程正在訪問同步對象時,另外一個線程必須等待。 
      Synchronized同步方法 
    現在我們可以對accessStudent方法實施同步。 
    public synchronized void  accessStudent() 
    再次運行程序,屏幕輸出如下: 
    a is running! 
    thread a set age to:49 
    thread a first  read age is:49 
    thread a second read age is:49 
    b is running! 
    thread b set age to:17 
    thread b first  read age is:17 
    thread b second read age is:17 

    加上了同步后,線程b必須等待線程a執行完畢后,線程b才開始執行。 

    對方法進行同步的代價是非常昂貴的。特別是當被同步的方法執行一個冗長的操作。這個方法執行會花費很長的時間,對這樣的方法進行同步可能會使系統性能成數量級的下降。 

    Synchronized同步塊 
      在accessStudent方法中,我們真實需要保護的是student變量,所以我們可以進行一個更細粒度的加鎖。我們僅僅對student相關的代碼塊進行同步。

     1synchronized(this{  
     2Random random = new Random();  
     3int age = random.nextInt(100);  
     4System.out.println("thread "+currentThreadName +" set age to:"+age);  
     5  
     6this.student.setAge(age);  
     7  
     8System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());  
     9try {  
    10Thread.sleep(5000);  
    11}
      
    12catch(InterruptedException ex) {  
    13    ex.printStackTrace();  
    14}
      
    15}
      

    運行方法后,屏幕輸出: 
    a is running! 
    thread a set age to:18 
    thread a first  read age is:18 
    b is running! 
    thread a second read age is:18 
    thread b set age to:62 
    thread b first  read age is:62 
    thread b second read age is:62 

    需要特別注意這個輸出結果。 
    這個執行過程比上面的方法同步要快得多了。 
    只有對student進行訪問的代碼是同步的,而其它與部份代碼卻是異步的了。而student的值并沒有被錯誤的修改。如果是在一個真實的系統中,accessStudent方法的操作又比較耗時的情況下。使用同步的速度幾乎與沒有同步一樣快。 

    使用同步鎖 
    稍微把上面的例子改一下,在ThreadDemo中有一個私有變量count,。 
       private int count=0; 
    在accessStudent()中, 線程每訪問一次,count都自加一次, 用來記數線程訪問的次數。 

    try {  
    this.count++;  
    Thread.sleep(
    5000);  
    }
    catch(InterruptedException ex) {  
        ex.printStackTrace();  
    }
      
    為了模擬線程,所以讓它每次自加后都睡眠5秒。 
    accessStuden()方法的完整代碼如下: 

     String currentThreadName = Thread.currentThread().getName();  
    System.out.println(currentThreadName
    +" is running!");  
      
    try {  
    this.count++;  
    Thread.sleep(
    5000);  
    }
    catch(InterruptedException ex) {  
        ex.printStackTrace();  
    }
      
     System.out.println(
    "thread "+currentThreadName+" read count:"+this.count);  
      
      
    synchronized(this{  
    Random random 
    = new Random();  
    int age = random.nextInt(100);  
    System.out.println(
    "thread "+currentThreadName +" set age to:"+age);  
      
    this.student.setAge(age);  
      
    System.out.println(
    "thread "+currentThreadName+" first  read age is:"+this.student.getAge());  
    try {  
    Thread.sleep(
    5000);  
    }
      
    catch(InterruptedException ex) {  
        ex.printStackTrace();  
    }
      
    }
      
    System.out.println(
    "thread "+currentThreadName +" second read age is:"+this.student.getAge()); 
    運行程序后,屏幕輸出: 
    a is running! 
    b is running! 
    thread a read count:2 
    thread a set age to:49 
    thread a first  read age is:49 
    thread b read count:2 
    thread a second read age is:49 
    thread b set age to:7 
    thread b first  read age is:7 
    thread b second read age is:7 

    我們仍然對student對象以synchronized(this)操作進行同步。 
    我們需要在兩個線程中共享count失敗。 

    所以仍然需要對count的訪問進行同步操作。 

     1synchronized(this{  
     2  try {  
     3  this.count++;  
     4  Thread.sleep(5000);  
     5  }
    catch(InterruptedException ex) {  
     6    ex.printStackTrace();  
     7  }
      
     8  }
      
     9  System.out.println("thread "+currentThreadName+" read count:"+this.count);  
    10    
    11   
    12  synchronized(this{  
    13  Random random = new Random();  
    14  int age = random.nextInt(100);  
    15  System.out.println("thread "+currentThreadName +" set age to:"+age);  
    16   
    17  this.student.setAge(age);  
    18   
    19  System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());  
    20  try {  
    21  Thread.sleep(5000);  
    22  }
      
    23  catch(InterruptedException ex) {  
    24    ex.printStackTrace();  
    25  }
      
    26  }
      
    27  System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());  
    28  long endTime = System.currentTimeMillis();  
    29  long spendTime = endTime - startTime;  
    30  System.out.println("花費時間:"+spendTime +"毫秒");  
    程序運行后,屏幕輸出 
    a is running! 
    b is running! 
    thread a read count:1 
    thread a set age to:97 
    thread a first  read age is:97 
    thread a second read age is:97 
    花費時間:10015毫秒 
    thread b read count:2 
    thread b set age to:47 
    thread b first  read age is:47 
    thread b second read age is:47 
    花費時間:20124毫秒 

    我們在同一個方法中,多次使用synchronized(this)進行加鎖。有可能會導致太多額外的等待。 
    應該使用不同的對象鎖進行同步。 

    設置兩個鎖對象,分別用于student和count的訪問加鎖。 

     1private Object studentLock = new Object();  
     2private Object countLock = new Object();  
     3  
     4accessStudent()方法如下:  
     5     long startTime = System.currentTimeMillis();  
     6        String currentThreadName = Thread.currentThread().getName();  
     7        System.out.println(currentThreadName+" is running!");  
     8       // System.out.println("first  read age is:"+this.student.getAge());  
     9  
    10         synchronized(countLock) {  
    11        try {  
    12        this.count++;  
    13        Thread.sleep(5000);  
    14        }
    catch(InterruptedException ex) {  
    15            ex.printStackTrace();  
    16        }
      
    17        }
      
    18        System.out.println("thread "+currentThreadName+" read count:"+this.count);  
    19          
    20         
    21        synchronized(studentLock) {  
    22        Random random = new Random();  
    23        int age = random.nextInt(100);  
    24        System.out.println("thread "+currentThreadName +" set age to:"+age);  
    25         
    26        this.student.setAge(age);  
    27         
    28        System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());  
    29        try {  
    30        Thread.sleep(5000);  
    31        }
      
    32        catch(InterruptedException ex) {  
    33            ex.printStackTrace();  
    34        }
      
    35        }
      
    36        System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());  
    37        long endTime = System.currentTimeMillis();  
    38        long spendTime = endTime - startTime;  
    39        System.out.println("花費時間:"+spendTime +"毫秒");  
    這樣對count和student加上了兩把不同的鎖。 

    運行程序后,屏幕輸出: 
    a is running! 
    b is running! 
    thread a read count:1 
    thread a set age to:48 
    thread a first  read age is:48 
    thread a second read age is:48 
    花費時間:10016毫秒 
    thread b read count:2 
    thread b set age to:68 
    thread b first  read age is:68 
    thread b second read age is:68 
    花費時間:20046毫秒 
    與兩次使用synchronized(this)相比,使用不同的對象鎖,在性能上可以得到更大的提升。 

    由此可見synchronized是實現java的同步機制。同步機制是為了實現同步多線程對相同資源的并發訪問控制。保證多線程之間的通信。 
    可見,同步的主要目的是保證多線程間的數據共享。同步會帶來巨大的性能開銷,所以同步操作應該是細粒度的。如果同步使用得當,帶來的性能開銷是微不足道的。使用同步真正的風險是復雜性和可能破壞資源安全,而不是性能。 


    ThreadLocal 
    由上面可以知道,使用同步是非常復雜的。并且同步會帶來性能的降低。Java提供了另外的一種方式,通過ThreadLocal可以很容易的編寫多線程程序。從字面上理解,很容易會把ThreadLocal誤解為一個線程的本地變量。其它ThreadLocal并不是代表當前線程,ThreadLocal其實是采用哈希表的方式來為每個線程都提供一個變量的副本。從而保證各個線程間數據安全。每個線程的數據不會被另外線程訪問和破壞。 

    我們把第一個例子用ThreadLocal來實現,但是我們需要些許改變。 
    Student并不是一個私有變量了,而是需要封裝在一個ThreadLocal對象中去。調用ThreadLocal的set方法,ThreadLocal會為每一個線程都保持一份Student變量的副本。所以對student的讀取操作都是通過ThreadLocal來進行的。 

     1protected Student getStudent() {  
     2    Student student = (Student)studentLocal.get();  
     3    if(student == null{  
     4        student = new Student();  
     5        studentLocal.set(student);  
     6    }
      
     7    return student;  
     8}
      
     9  
    10protected void setStudent(Student student) {  
    11    studentLocal.set(student);  
    12}
      
    accessStudent()方法需要做一些改變。通過調用getStudent()方法來獲得當前線程的Student變量,如果當前線程不存在一個Student變量,getStudent方法會創建一個新的Student變量,并設置在當前線程中。 
        Student student = getStudent(); 
        student.setAge(age); 
    accessStudent()方法中無需要任何同步代碼。 

    完整的代碼清單如下: 
    TreadLocalDemo.java 

     1public class TreadLocalDemo implements Runnable {  
     2   private final static  ThreadLocal studentLocal = new ThreadLocal();  
     3     
     4   public static void main(String[] agrs) {  
     5       TreadLocalDemo td = new TreadLocalDemo();  
     6         Thread t1 = new Thread(td,"a");  
     7         Thread t2 = new Thread(td,"b");  
     8          
     9        t1.start();  
    10        t2.start();  
    11         
    12         
    13  
    14  
    15      }
      
    16     
    17    /* (non-Javadoc) 
    18     * @see java.lang.Runnable#run() 
    19     */
      
    20    public void run() {  
    21         accessStudent();  
    22    }
      
    23  
    24    public  void  accessStudent() {  
    25          
    26        String currentThreadName = Thread.currentThread().getName();  
    27        System.out.println(currentThreadName+" is running!");  
    28        Random random = new Random();  
    29        int age = random.nextInt(100);  
    30        System.out.println("thread "+currentThreadName +" set age to:"+age);  
    31        Student student = getStudent();  
    32        student.setAge(age);  
    33        System.out.println("thread "+currentThreadName+" first  read age is:"+student.getAge());  
    34        try {  
    35        Thread.sleep(5000);  
    36        }
      
    37        catch(InterruptedException ex) {  
    38            ex.printStackTrace();  
    39        }
      
    40        System.out.println("thread "+currentThreadName +" second read age is:"+student.getAge());  
    41          
    42    }
      
    43      
    44    protected Student getStudent() {  
    45        Student student = (Student)studentLocal.get();  
    46        if(student == null{  
    47            student = new Student();  
    48            studentLocal.set(student);  
    49        }
      
    50        return student;  
    51    }
      
    52      
    53    protected void setStudent(Student student) {  
    54        studentLocal.set(student);  
    55    }
      
    56}

    運行程序后,屏幕輸出: 
    b is running! 
    thread b set age to:0 
    thread b first  read age is:0 
    a is running! 
    thread a set age to:17 
    thread a first  read age is:17 
    thread b second read age is:0 
    thread a second read age is:17 

    可見,使用ThreadLocal后,我們不需要任何同步代碼,卻能夠保證我們線程間數據的安全。 
    而且,ThreadLocal的使用也非常的簡單。 
    我們僅僅需要使用它提供的兩個方法 
    void set(Object obj) 設置當前線程的變量的副本的值。 
    Object get() 返回當前線程的變量副本 

    另外ThreadLocal還有一個protected的initialValue()方法。返回變量副本在當前線程的初始值。默認為null 

    ThreadLocal是怎么做到為每個線程都維護一個變量的副本的呢? 
    我們可以猜測到ThreadLocal的一個簡單實現 

     

     1public class ThreadLocal  
     2{  
     3 private Map values = Collections.synchronizedMap(new HashMap());  
     4 public Object get()  
     5 {  
     6  Thread curThread = Thread.currentThread();   
     7  Object o = values.get(curThread);   
     8  if (o == null && !values.containsKey(curThread))  
     9  {  
    10   o = initialValue();  
    11   values.put(curThread, o);   
    12  }
      
    13  return o;   
    14 }
      
    15  
    16 public void set(Object newValue)  
    17 {  
    18  values.put(Thread.currentThread(), newValue);  
    19 }
      
    20  
    21 public Object initialValue()  
    22 {  
    23  return null;   
    24 }
      
    25}
      

    由此可見,ThreadLocal通過一個Map來為每個線程都持有一個變量副本。這個map以當前線程為key。與synchronized相比,ThreadLocal是以空間換時間的策略來實現多線程程序。 

    Synchronized還是ThreadLocal? 
    ThreadLocal以空間換取時間,提供了一種非常簡便的多線程實現方式。因為多個線程并發訪問無需進行等待,所以使用ThreadLocal會獲得更大的性能。雖然使用ThreadLocal會帶來更多的內存開銷,但這點開銷是微不足道的。因為保存在ThreadLocal中的對象,通常都是比較小的對象。另外使用ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。 
    ThreadLocal和Synchonized都用于解決多線程并發訪問。但是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得數據共享。 
    Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離。 
    當然ThreadLocal并不能替代synchronized,它們處理不同的問題域。Synchronized用于實現同步機制,比ThreadLocal更加復雜。

    posted on 2008-10-06 12:13 rainman 閱讀(744) 評論(1)  編輯  收藏 所屬分類: java多線程

    評論

    # re: ThreadLocal與synchronize  回復  更多評論   

    之所以用不同的對象作為鎖,是希望每一個同步塊同時都只能被一個線程所訪問,而不是所有同步塊只能被一個線程所訪問,這是我的理解,不知道是否正確。
    2008-10-06 14:40 | rainman
    主站蜘蛛池模板: 亚洲乱码一区二区三区在线观看| 免费精品国产日韩热久久| 久久精品国产亚洲AV蜜臀色欲 | av免费不卡国产观看| 麻豆69堂免费视频| 亚洲人成在线免费观看| 亚洲AV无码乱码在线观看富二代 | 亚洲天堂一区二区| 国产精品亚洲不卡一区二区三区| 国产黄色免费观看| 国产成人亚洲毛片| 在线观看亚洲AV日韩AV| 亚洲精品自拍视频| 久久久无码精品亚洲日韩按摩| 一级女人18毛片免费| 亚洲免费视频网站| 中文字幕无码一区二区免费| 九九免费观看全部免费视频| 亚洲高清毛片一区二区| 亚洲色一区二区三区四区| 亚洲成人黄色网址| 亚洲中文无码a∨在线观看| 久久精品国产亚洲精品2020| 国产亚洲A∨片在线观看| 亚洲色欲久久久综合网东京热| 久久青草免费91线频观看站街| 久久精品国产亚洲AV忘忧草18| 国产成人高清精品免费软件| 成人免费视频软件网站| 人禽杂交18禁网站免费| 亚洲国产精品免费在线观看| 亚洲精品视频在线观看免费| 18禁男女爽爽爽午夜网站免费| 国产亚洲视频在线播放大全| 无码天堂va亚洲va在线va| 亚洲a无码综合a国产av中文| 亚洲中文无码永久免费| 亚洲Av永久无码精品黑人 | 中文字幕成人免费高清在线| 成人A片产无码免费视频在线观看| 亚洲欧美日韩综合俺去了|