<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          對Java線程安全與不安全的理解

          共 11324字,需瀏覽 23分鐘

           ·

          2021-06-13 18:17

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

            

          當(dāng)我們查看JDK API的時候,總會發(fā)現(xiàn)一些類說明寫著,線程安全或者線程不安全,比如說到StringBuilder中,有這么一句,“將StringBuilder 的實例用于多個線程是不安全的。如果需要這樣的同步,則建議使用StringBuffer。”,提到StringBuffer時,說到“StringBuffer是線程安全的可變字符序列,一個類似于String的字符串緩沖區(qū),雖然在任意時間點(diǎn)上它都包含某種特定的字符序列,但通過某些方法調(diào)用可以改變該序列的長度和內(nèi)容??蓪⒆址彌_區(qū)安全地用于多個線程??梢栽诒匾獣r對這些方法進(jìn)行同步,因此任意特定實例上的所有操作就好像是以串行順序發(fā)生的,該順序與所涉及的每個線程進(jìn)行的方法調(diào)用順序一致”。StringBuilder是一個可變的字符序列,此類提供一個與StringBuffe兼容的API,但不保證同步。該類被設(shè)計用作StringBuffer的一個簡易替換,用在字符串緩沖區(qū)被單個線程使用的時候(這種情況很普遍)。如果可能,建議優(yōu)先采用該類,因為在大多數(shù)實現(xiàn)中,它比StringBuffer要快。將StringBuilder的實例用于多個線程是不安全的,如果需要這樣的同步,則建議使用StringBuffer。

             根據(jù)以上JDK文檔中對StringBuffer和StringBuilder的描述,得到對String、StringBuilder與StringBuffer三者使用情況的總結(jié):
             1、如果要操作少量的數(shù)據(jù)用String
             2、單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù)StringBuilder
             3、多線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù)StringBuffer

             那么下面手動創(chuàng)建一個線程不安全的類,然后在多線程中使用這個類,看看有什么效果。

          public class Count {
              private int num;
              //public void count() {
              //    for(int i = 1; i <= 100; i++) {
              //        num += i;
              //    }
              //    System.out.println(Thread.currentThread().getName() + "-" + num);
              //}

              public int getNum() {
                  return num;
              }

              public void increment(int i) {
                  num = num + i;
              }
          }


             在這個類中的increment方法實現(xiàn)num變量與指定變量作加法。

          public class ThreadTest {
              public static void main(String[] args) {
                  Runnable runnable = new Runnable() {
                      Count count = new Count();
                      @Override
                      public void run() {
                          for (int i = 0; i < 1000; i++) {
                              count.increment(1);
                          }
                          System.out.println(Thread.currentThread().getName() + "-" + count.getNum());
                          try {
                              Thread.sleep(2);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  };

                  for(int i = 0; i < 10; i++) {
                      Thread thread = new Thread(runnable);
                      thread.start();
                  }
              }
          }

             這里啟動了10個線程,看一下輸出結(jié)果:

          Thread-0-1660
          Thread-2-2660
          Thread-3-3660
          Thread-1-1660
          Thread-4-4882
          Thread-5-5579
          Thread-6-6579
          Thread-7-7579
          Thread-8-8579
          Thread-9-9579

             期望的結(jié)果是每個線程都能輸出1000,但實際上每個線程的輸出值都不一樣而且不是整數(shù),多運(yùn)行幾次每次的輸出結(jié)果都不一樣,要想得到我們期望的結(jié)果,有幾種解決方案:

             1、將累加邏輯移到Count類中,并且使用局部變量而不是成員變量;

          public class Count {
              public void count() {
                  int number = 0;
                  for(int i = 0; i < 1000; i++) {
                      number += 1;
                  }
                  System.out.println(Thread.currentThread().getName() + "-" + number);
              }
          }

          public class ThreadTest {
              public static void main(String[] args) {
                  Runnable runnable = new Runnable() {
                      Count count = new Count();
                      @Override
                      public void run() {
                          count.count();
                          try {
                              Thread.sleep(2);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  };

                  for(int i = 0; i < 10; i++) {
                      Thread thread = new Thread(runnable);
                      thread.start();
                  }
              }
          }

             運(yùn)行結(jié)果如下:

          Thread-0-1000
          Thread-3-1000
          Thread-4-1000
          Thread-1-1000
          Thread-2-1000
          Thread-5-1000
          Thread-6-1000
          Thread-7-1000
          Thread-8-1000
          Thread-9-1000

             2、將線程類成員變量拿到run方法中,這時count引用是線程內(nèi)的局部變量;

          public class ThreadTest {
              public static void main(String[] args) {
                  Runnable runnable = new Runnable() {
                      @Override
                      public void run() {
                          Count count = new Count();
                          for (int i = 0; i < 1000; i++) {
                              count.increment(1);
                          }
                          System.out.println(Thread.currentThread().getName() + "-" + count.getNum());
                          try {
                              Thread.sleep(2);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  };

                  for(int i = 0; i < 10; i++) {
                      Thread thread = new Thread(runnable);
                      thread.start();
                  }
              }
          }


             運(yùn)行結(jié)果如下:

          Thread-1-1000
          Thread-3-1000
          Thread-2-1000
          Thread-0-1000
          Thread-5-1000
          Thread-4-1000
          Thread-6-1000
          Thread-7-1000
          Thread-8-1000
          Thread-9-1000

             3、每次啟動一個線程使用不同的線程類,不推薦。

             通過上述測試,我們發(fā)現(xiàn),存在成員變量的類(即有狀態(tài)的類)用于多線程時是不安全的,不安全體現(xiàn)在這個成員變量可能發(fā)生非原子性的操作,而變量定義在方法內(nèi)也就是局部變量是線程安全的。想想在使用struts1時,不推薦創(chuàng)建成員變量,因為action是單例的,如果創(chuàng)建了成員變量,就會存在線程不安全的隱患,而struts2是每一次請求都會創(chuàng)建一個action,就不用考慮線程安全的問題。所以,日常開發(fā)中,通常需要考慮成員變量或者說全局變量在多線程環(huán)境下,是否會引發(fā)一些問題

             要說明線程同步問題首先要說明Java線程的兩個特性,可見性和有序性

             多個線程之間是不能直接傳遞數(shù)據(jù)進(jìn)行交互的,它們之間的交互只能通過共享變量來實現(xiàn)。拿上面的例子來說明,在多個線程之間共享了Count類的一個實例,這個對象是被創(chuàng)建在主內(nèi)存(堆內(nèi)存)中,每個線程都有自己的工作內(nèi)存(線程棧),工作內(nèi)存存儲了主內(nèi)存count對象的一個副本,當(dāng)線程操作count對象時,首先從主內(nèi)存復(fù)制count對象到工作內(nèi)存中,然后執(zhí)行代碼count.count(),改變了num值,最后用工作內(nèi)存中的count刷新主內(nèi)存的 count。當(dāng)一個對象在多個工作內(nèi)存中都存在副本時,如果一個工作內(nèi)存刷新了主內(nèi)存中的共享變量,其它線程也應(yīng)該能夠看到被修改后的值,此為可見性

             多個線程執(zhí)行時,CPU對線程的調(diào)度是隨機(jī)的,我們不知道當(dāng)前程序被執(zhí)行到哪步就切換到了下一個線程,一個最經(jīng)典的例子就是銀行匯款問題,一個銀行賬戶存款100,這時一個人從該賬戶取10元,同時另一個人向該賬戶匯10元,那么余額應(yīng)該還是100。那么此時可能發(fā)生這種情況,A線程負(fù)責(zé)取款,B線程負(fù)責(zé)匯款,A從主內(nèi)存讀到100,B從主內(nèi)存讀到100,A執(zhí)行減10操作,并將數(shù)據(jù)刷新到主內(nèi)存,這時主內(nèi)存數(shù)據(jù)100-10=90,而B內(nèi)存執(zhí)行加10操作,并將數(shù)據(jù)刷新到主內(nèi)存,最后主內(nèi)存數(shù)據(jù)100+10=110,顯然這是一個嚴(yán)重的問題,我們要保證A線程和B線程有序執(zhí)行,先取款后匯款或者先匯款后取款,此為有序性。

             在Web開發(fā)方面,Servlet是否是線程安全的呢?

             Servlet不是線程安全的。要解釋為什么Servlet為什么不是線程安全的,需要了解Servlet容器(如Tomcat)是如何響應(yīng)HTTP請求的。當(dāng)Tomcat接收到Client的HTTP請求時,Tomcat從線程池中取出一個線程,之后找到該請求對應(yīng)的Servlet對象并進(jìn)行初始化,之后調(diào)用service()方法。要注意的是每一個Servlet對象在Tomcat容器中只有一個實例對象,即是單例模式。如果多個HTTP請求請求的是同一個Servlet,那么這兩個HTTP請求對應(yīng)的線程將并發(fā)調(diào)用Servlet的service()方法。如果的Thread1和Thread2調(diào)用了同一個Servlet1,Servlet1中定義了成員變量或靜態(tài)變量,那么可能會發(fā)生線程安全問題(因為所有的線程都可能使用這些變量)

             像Servlet這樣的類,在Web 容器中創(chuàng)建以后,會被傳遞給每個訪問Web應(yīng)用的用戶線程執(zhí)行,這個類就不是線程安全的。但這并不意味著一定會引發(fā)線程安全問題,如果Servlet類里沒有成員變量,即使多線程同時執(zhí)行這個Servlet實例的方法,也不會造成成員變量沖突。這種對象被稱作無狀態(tài)對象,也就是說對象不記錄狀態(tài),執(zhí)行這個對象的任何方法都不會改變對象的狀態(tài),也就不會有線程安全問題了。事實上,Web開發(fā)實踐中,常見的Service類、DAO類,都被設(shè)計成無狀態(tài)對象,所以雖然我們開發(fā)的Web應(yīng)用都是多線程的應(yīng)用,因為Web容器一定會創(chuàng)建多線程來執(zhí)行我們的代碼,但是我們開發(fā)中卻可以很少考慮線程安全的問題。



          版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。

          本文鏈接:

          https://blog.csdn.net/fuzhongmin05/article/details/59110866







          瀏覽 41
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一区二区三区福利视频 | 天天爱夜夜操 | 蜜桃av秘 无码一区三区。 | 成人亚洲A片Ⅴ一区二区三区动漫 | 青青草成人免费观看 |