<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, 評(píng)論 - 2, 引用 - 0
    數(shù)據(jù)加載中……

    ThreadLocal與synchronize

    Java良好的支持多線程。使用java,我們可以很輕松的編程一個(gè)多線程程序。但是使用多線程可能會(huì)引起并發(fā)訪問(wèn)的問(wèn)題。synchronized和ThreadLocal都是用來(lái)解決多線程并發(fā)訪問(wèn)的問(wèn)題。大家可能對(duì)synchronized較為熟悉,而對(duì)ThreadLocal就要陌生得多了。 
    并發(fā)問(wèn)題。當(dāng)一個(gè)對(duì)象被兩個(gè)線程同時(shí)訪問(wèn)時(shí),可能有一個(gè)線程會(huì)得到不可預(yù)期的結(jié)果。 

    一個(gè)簡(jiǎn)單的java類(lèi)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}
      
    一個(gè)多線程類(lèi)ThreadDemo. 
    這個(gè)類(lèi)有一個(gè)Student的私有變量,在run方法中,它隨機(jī)產(chǎn)生一個(gè)整數(shù)。然后設(shè)置到student變量中,從student中讀取設(shè)置后的值。然后睡眠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}
      
    運(yùn)行這個(gè)程序,屏幕輸出如下: 
    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在同一個(gè)方法中,第一次讀取student的age值與第二次讀取值不一致。這就是出現(xiàn)了并發(fā)問(wèn)題。 

    synchronized 
    上面的例子,我們模似了一個(gè)并發(fā)問(wèn)題。Java提供了同步機(jī)制來(lái)解決并發(fā)問(wèn)題。synchonzied關(guān)鍵字可以用來(lái)同步變量,方法,甚至同步一個(gè)代碼塊。 
    使用了同步后,一個(gè)線程正在訪問(wèn)同步對(duì)象時(shí),另外一個(gè)線程必須等待。 
      Synchronized同步方法 
    現(xiàn)在我們可以對(duì)accessStudent方法實(shí)施同步。 
    public synchronized void  accessStudent() 
    再次運(yùn)行程序,屏幕輸出如下: 
    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執(zhí)行完畢后,線程b才開(kāi)始執(zhí)行。 

    對(duì)方法進(jìn)行同步的代價(jià)是非常昂貴的。特別是當(dāng)被同步的方法執(zhí)行一個(gè)冗長(zhǎng)的操作。這個(gè)方法執(zhí)行會(huì)花費(fèi)很長(zhǎng)的時(shí)間,對(duì)這樣的方法進(jìn)行同步可能會(huì)使系統(tǒng)性能成數(shù)量級(jí)的下降。 

    Synchronized同步塊 
      在accessStudent方法中,我們真實(shí)需要保護(hù)的是student變量,所以我們可以進(jìn)行一個(gè)更細(xì)粒度的加鎖。我們僅僅對(duì)student相關(guān)的代碼塊進(jìn)行同步。

     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}
      

    運(yùn)行方法后,屏幕輸出: 
    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 

    需要特別注意這個(gè)輸出結(jié)果。 
    這個(gè)執(zhí)行過(guò)程比上面的方法同步要快得多了。 
    只有對(duì)student進(jìn)行訪問(wèn)的代碼是同步的,而其它與部份代碼卻是異步的了。而student的值并沒(méi)有被錯(cuò)誤的修改。如果是在一個(gè)真實(shí)的系統(tǒng)中,accessStudent方法的操作又比較耗時(shí)的情況下。使用同步的速度幾乎與沒(méi)有同步一樣快。 

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

    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()); 
    運(yùn)行程序后,屏幕輸出: 
    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 

    我們?nèi)匀粚?duì)student對(duì)象以synchronized(this)操作進(jìn)行同步。 
    我們需要在兩個(gè)線程中共享count失敗。 

    所以仍然需要對(duì)count的訪問(wèn)進(jìn)行同步操作。 

     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("花費(fèi)時(shí)間:"+spendTime +"毫秒");  
    程序運(yùn)行后,屏幕輸出 
    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 
    花費(fèi)時(shí)間: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 
    花費(fèi)時(shí)間:20124毫秒 

    我們?cè)谕粋€(gè)方法中,多次使用synchronized(this)進(jìn)行加鎖。有可能會(huì)導(dǎo)致太多額外的等待。 
    應(yīng)該使用不同的對(duì)象鎖進(jìn)行同步。 

    設(shè)置兩個(gè)鎖對(duì)象,分別用于student和count的訪問(wèn)加鎖。 

     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("花費(fèi)時(shí)間:"+spendTime +"毫秒");  
    這樣對(duì)count和student加上了兩把不同的鎖。 

    運(yùn)行程序后,屏幕輸出: 
    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 
    花費(fèi)時(shí)間: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 
    花費(fèi)時(shí)間:20046毫秒 
    與兩次使用synchronized(this)相比,使用不同的對(duì)象鎖,在性能上可以得到更大的提升。 

    由此可見(jiàn)synchronized是實(shí)現(xiàn)java的同步機(jī)制。同步機(jī)制是為了實(shí)現(xiàn)同步多線程對(duì)相同資源的并發(fā)訪問(wèn)控制。保證多線程之間的通信。 
    可見(jiàn),同步的主要目的是保證多線程間的數(shù)據(jù)共享。同步會(huì)帶來(lái)巨大的性能開(kāi)銷(xiāo),所以同步操作應(yīng)該是細(xì)粒度的。如果同步使用得當(dāng),帶來(lái)的性能開(kāi)銷(xiāo)是微不足道的。使用同步真正的風(fēng)險(xiǎn)是復(fù)雜性和可能破壞資源安全,而不是性能。 


    ThreadLocal 
    由上面可以知道,使用同步是非常復(fù)雜的。并且同步會(huì)帶來(lái)性能的降低。Java提供了另外的一種方式,通過(guò)ThreadLocal可以很容易的編寫(xiě)多線程程序。從字面上理解,很容易會(huì)把ThreadLocal誤解為一個(gè)線程的本地變量。其它ThreadLocal并不是代表當(dāng)前線程,ThreadLocal其實(shí)是采用哈希表的方式來(lái)為每個(gè)線程都提供一個(gè)變量的副本。從而保證各個(gè)線程間數(shù)據(jù)安全。每個(gè)線程的數(shù)據(jù)不會(huì)被另外線程訪問(wèn)和破壞。 

    我們把第一個(gè)例子用ThreadLocal來(lái)實(shí)現(xiàn),但是我們需要些許改變。 
    Student并不是一個(gè)私有變量了,而是需要封裝在一個(gè)ThreadLocal對(duì)象中去。調(diào)用ThreadLocal的set方法,ThreadLocal會(huì)為每一個(gè)線程都保持一份Student變量的副本。所以對(duì)student的讀取操作都是通過(guò)ThreadLocal來(lái)進(jìn)行的。 

     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()方法需要做一些改變。通過(guò)調(diào)用getStudent()方法來(lái)獲得當(dāng)前線程的Student變量,如果當(dāng)前線程不存在一個(gè)Student變量,getStudent方法會(huì)創(chuàng)建一個(gè)新的Student變量,并設(shè)置在當(dāng)前線程中。 
        Student student = getStudent(); 
        student.setAge(age); 
    accessStudent()方法中無(wú)需要任何同步代碼。 

    完整的代碼清單如下: 
    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}

    運(yùn)行程序后,屏幕輸出: 
    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 

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

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

    ThreadLocal是怎么做到為每個(gè)線程都維護(hù)一個(gè)變量的副本的呢? 
    我們可以猜測(cè)到ThreadLocal的一個(gè)簡(jiǎn)單實(shí)現(xiàn) 

     

     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}
      

    由此可見(jiàn),ThreadLocal通過(guò)一個(gè)Map來(lái)為每個(gè)線程都持有一個(gè)變量副本。這個(gè)map以當(dāng)前線程為key。與synchronized相比,ThreadLocal是以空間換時(shí)間的策略來(lái)實(shí)現(xiàn)多線程程序。 

    Synchronized還是ThreadLocal? 
    ThreadLocal以空間換取時(shí)間,提供了一種非常簡(jiǎn)便的多線程實(shí)現(xiàn)方式。因?yàn)槎鄠€(gè)線程并發(fā)訪問(wèn)無(wú)需進(jìn)行等待,所以使用ThreadLocal會(huì)獲得更大的性能。雖然使用ThreadLocal會(huì)帶來(lái)更多的內(nèi)存開(kāi)銷(xiāo),但這點(diǎn)開(kāi)銷(xiāo)是微不足道的。因?yàn)楸4嬖赥hreadLocal中的對(duì)象,通常都是比較小的對(duì)象。另外使用ThreadLocal不能使用原子類(lèi)型,只能使用Object類(lèi)型。ThreadLocal的使用比synchronized要簡(jiǎn)單得多。 
    ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問(wèn)。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別。synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問(wèn)。而ThreadLocal為每一個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問(wèn)到的并不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。而Synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享。 
    Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離。 
    當(dāng)然ThreadLocal并不能替代synchronized,它們處理不同的問(wèn)題域。Synchronized用于實(shí)現(xiàn)同步機(jī)制,比ThreadLocal更加復(fù)雜。

    posted on 2008-10-06 12:13 rainman 閱讀(745) 評(píng)論(1)  編輯  收藏 所屬分類(lèi): java多線程

    評(píng)論

    # re: ThreadLocal與synchronize  回復(fù)  更多評(píng)論   

    之所以用不同的對(duì)象作為鎖,是希望每一個(gè)同步塊同時(shí)都只能被一個(gè)線程所訪問(wèn),而不是所有同步塊只能被一個(gè)線程所訪問(wèn),這是我的理解,不知道是否正確。
    2008-10-06 14:40 | rainman
    主站蜘蛛池模板: 亚洲免费精彩视频在线观看| 99热精品在线免费观看| 免费一级毛片正在播放| 国产成人亚洲精品91专区高清| 日韩免费视频在线观看| 国产亚洲欧美在线观看| 国产精品酒店视频免费看| 在线观看亚洲免费视频| vvvv99日韩精品亚洲| 国产高清视频免费在线观看| 国产午夜亚洲不卡| 日韩精品无码免费专区网站| 亚洲大尺度无码专区尤物| 久草福利资源网站免费| 亚洲高清中文字幕综合网| 99爱在线精品免费观看| 亚洲乱码卡一卡二卡三| a级毛片无码免费真人| 亚洲精品无码不卡在线播放| 国产在线19禁免费观看国产 | 在线播放免费人成视频网站| 亚洲国产精品尤物YW在线观看| 国产视频精品免费视频| 久久亚洲精品中文字幕三区| 久久久久免费精品国产小说| 亚洲高清免费在线观看| 免费a级毛片高清视频不卡| 亚洲AV永久无码精品网站在线观看 | 亚洲国产91精品无码专区| 精品97国产免费人成视频| 日韩va亚洲va欧洲va国产| 亚洲免费中文字幕| 亚洲人成色在线观看| 亚洲人成电影网站国产精品| 三级网站在线免费观看| 亚洲网站在线免费观看| 在线jlzzjlzz免费播放| 免费人妻精品一区二区三区| 国产亚洲一区二区在线观看| 亚洲高清视频免费| 亚洲乱亚洲乱妇24p|