1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        面試官:談?wù)勀銓?duì)Java線程安全與不安全的理解

        共 11123字,需瀏覽 23分鐘

         ·

        2021-06-28 15:21

        當(dāng)我們查看JDK API的時(shí)候,總會(huì)發(fā)現(xiàn)一些類說(shuō)明寫著,線程安全或者線程不安全,比如說(shuō)到StringBuilder中,有這么一句,“將StringBuilder 的實(shí)例用于多個(gè)線程是不安全的。如果需要這樣的同步,則建議使用StringBuffer?!?/strong>

        提到StringBuffer時(shí),說(shuō)到“StringBuffer是線程安全的可變字符序列,一個(gè)類似于String的字符串緩沖區(qū),雖然在任意時(shí)間點(diǎn)上它都包含某種特定的字符序列,但通過(guò)某些方法調(diào)用可以改變?cè)撔蛄械拈L(zhǎng)度和內(nèi)容??蓪⒆址彌_區(qū)安全地用于多個(gè)線程??梢栽诒匾獣r(shí)對(duì)這些方法進(jìn)行同步,因此任意特定實(shí)例上的所有操作就好像是以串行順序發(fā)生的,該順序與所涉及的每個(gè)線程進(jìn)行的方法調(diào)用順序一致”。

        StringBuilder是一個(gè)可變的字符序列,此類提供一個(gè)與StringBuffe兼容的API,但不保證同步。該類被設(shè)計(jì)用作StringBuffer的一個(gè)簡(jiǎn)易替換,用在字符串緩沖區(qū)被單個(gè)線程使用的時(shí)候(這種情況很普遍)。如果可能,建議優(yōu)先采用該類,因?yàn)樵诖蠖鄶?shù)實(shí)現(xiàn)中,它比StringBuffer要快。將StringBuilder的實(shí)例用于多個(gè)線程是不安全的,如果需要這樣的同步,則建議使用StringBuffer。

        根據(jù)以上JDK文檔中對(duì)StringBuffer和StringBuilder的描述,得到對(duì)String、StringBuilder與StringBuffer三者使用情況的總結(jié):

        • 如果要操作少量的數(shù)據(jù)用String
        • 單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù)StringBuilder
        • 多線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù)StringBuffer

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

        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;
            }
        }

        在這個(gè)類中的increment方法實(shí)現(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();
                }
            }
        }

        這里啟動(dòng)了10個(gè)線程,看一下輸出結(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é)果是每個(gè)線程都能輸出1000,但實(shí)際上每個(gè)線程的輸出值都不一樣而且不是整數(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方法中,這時(shí)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、每次啟動(dòng)一個(gè)線程使用不同的線程類,不推薦。

        通過(guò)上述測(cè)試,我們發(fā)現(xiàn),存在成員變量的類(即有狀態(tài)的類)用于多線程時(shí)是不安全的,不安全體現(xiàn)在這個(gè)成員變量可能發(fā)生非原子性的操作,而變量定義在方法內(nèi)也就是局部變量是線程安全的。

        想想在使用struts1時(shí),不推薦創(chuàng)建成員變量,因?yàn)閍ction是單例的,如果創(chuàng)建了成員變量,就會(huì)存在線程不安全的隱患,而struts2是每一次請(qǐng)求都會(huì)創(chuàng)建一個(gè)action,就不用考慮線程安全的問題。所以,日常開發(fā)中,通常需要考慮成員變量或者說(shuō)全局變量在多線程環(huán)境下,是否會(huì)引發(fā)一些問題。

        要說(shuō)明線程同步問題首先要說(shuō)明Java線程的兩個(gè)特性,可見性和有序性。

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

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

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

        Servlet不是線程安全的。要解釋為什么Servlet為什么不是線程安全的,需要了解Servlet容器(如Tomcat)是如何響應(yīng)HTTP請(qǐng)求的。當(dāng)Tomcat接收到Client的HTTP請(qǐng)求時(shí),Tomcat從線程池中取出一個(gè)線程,之后找到該請(qǐng)求對(duì)應(yīng)的Servlet對(duì)象并進(jìn)行初始化,之后調(diào)用service()方法。

        要注意的是每一個(gè)Servlet對(duì)象在Tomcat容器中只有一個(gè)實(shí)例對(duì)象,即是單例模式。如果多個(gè)HTTP請(qǐng)求請(qǐng)求的是同一個(gè)Servlet,那么這兩個(gè)HTTP請(qǐng)求對(duì)應(yīng)的線程將并發(fā)調(diào)用Servlet的service()方法。如果的Thread1和Thread2調(diào)用了同一個(gè)Servlet1,Servlet1中定義了成員變量或靜態(tài)變量,那么可能會(huì)發(fā)生線程安全問題(因?yàn)樗械木€程都可能使用這些變量)。

        像Servlet這樣的類,在Web 容器中創(chuàng)建以后,會(huì)被傳遞給每個(gè)訪問Web應(yīng)用的用戶線程執(zhí)行,這個(gè)類就不是線程安全的。但這并不意味著一定會(huì)引發(fā)線程安全問題,如果Servlet類里沒有成員變量,即使多線程同時(shí)執(zhí)行這個(gè)Servlet實(shí)例的方法,也不會(huì)造成成員變量沖突。

        這種對(duì)象被稱作無(wú)狀態(tài)對(duì)象,也就是說(shuō)對(duì)象不記錄狀態(tài),執(zhí)行這個(gè)對(duì)象的任何方法都不會(huì)改變對(duì)象的狀態(tài),也就不會(huì)有線程安全問題了。事實(shí)上,Web開發(fā)實(shí)踐中,常見的Service類、DAO類,都被設(shè)計(jì)成無(wú)狀態(tài)對(duì)象,所以雖然我們開發(fā)的Web應(yīng)用都是多線程的應(yīng)用,因?yàn)閃eb容器一定會(huì)創(chuàng)建多線程來(lái)執(zhí)行我們的代碼,但是我們開發(fā)中卻可以很少考慮線程安全的問題。

        來(lái)源:blog.csdn.net/fuzhongmin05/article/details/59110866

        我已經(jīng)更新了我的《10萬(wàn)字Springboot經(jīng)典學(xué)習(xí)筆記》中,點(diǎn)擊下面小卡片,進(jìn)入【Java開發(fā)寶典】,回復(fù):筆記,即可免費(fèi)獲取。

        點(diǎn)贊是最大的支持 

        瀏覽 56
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            一级片手机在线观看 | 在教室伦流澡到高潮h女女视 | 熟妇少妇任你躁在线无码 | 无码人妻一区二区三区四区老鸭窝 | 玖玖网 | 午夜福利黄 | 差漫画 | 婷婷黄色| 成人做爰黄AA片啪啪声 | 欧美超级操逼视频免费观看 |