1. 程式人生 > 其它 >對執行緒的理解?

對執行緒的理解?

一、對執行緒和程序的理解?(面試過程被問到過)

         一個程序是一個程式一次執行的過程,或者是正在執行的一個程式,是一個動態的過程。程序可以分為多個執行緒,是一個程式內部的執行路徑。如果一個程序可以同時進行多個執行緒,那麼這麼程序就是支援多執行緒的;執行緒是排程和執行的基本單位,每個執行緒都擁有自己獨立的程式計數器與執行棧;一個程序中的多個執行緒可以共享相同的資源,這就會帶來一些安全隱患(比如:產生死鎖、讀髒資料等等)。

二、建立執行執行緒的方式有四種(面試過程被問到過):

  1、繼承Thread類:

        1、定義子類繼承Thread類;

        2、子類中重寫run()方法;

        3、建立Thread子類物件,即建立了執行緒物件;

        4、呼叫執行緒物件的start方法:啟動執行緒,呼叫run()方法。

      注意:如果手動呼叫的run()方法並不算是多執行緒;run()方法由JVM呼叫,何時呼叫由CPU說了算;想要啟動多執行緒必須呼叫start()方法;

         一個執行緒物件只能呼叫一次start方法啟動,如果重複呼叫則會丟擲lllegalThreadStateException。

      1.1 Thread中相關方法:static voi yeild()//執行緒讓步,暫停當前執行緒把機會讓給優先順序更高或者相同的執行緒

                      static void sleep(long millis)// 暫停當前執行緒並且暫停的時長為你設定的時間(單位:毫秒),結束後重新進行排隊。

                      區別:執行緒呼叫sleep()方法後進入堵塞狀態,醒來後因為沒有釋放鎖直接進入就緒狀態,執行yeild後也沒有釋放鎖於是進入了就緒狀態。sleep()方法使用時需要處理InterruptException異常,而yield沒有。sleep()執行後進入堵塞狀態(記時等待)醒來後進入就緒狀態也可能是堵塞狀態,而yeild()是直接進入就緒狀態。由於兩個方法在當前正在執行的執行緒上工作

,所以其他處於等待狀態的執行緒呼叫它們是沒有意義的。

      1.2 run()方法與start()方法的區別聯絡:呼叫start()方法的本質就是呼叫run()方法,為什麼要這樣呢?當我們new 一個新的執行緒物件後,執行緒進入新建狀態,start()方法作用就是使執行緒進入就緒的狀態,當分配到時間片後就可以執行。 start方法會執行執行緒前的相應的準備工作,然後執行run()方法執行執行緒體,這才是一個真正的多執行緒。 如果直接執行run(),就相當於在一個主執行緒中呼叫一個普通的run()方法,在主執行緒裡執行,並沒有體現多執行緒的作用。

·      1.3 sleep()與wait()的區別:sleep()是Thread下的靜態方法,wait()是Object類下的方法;

                    sleep()不釋放鎖,wait()釋放鎖;

                    sleep()常用於暫停執行,wait()常用於執行緒間的通訊;

                    wait()用完後執行緒不會自動執行,必須呼叫notify()或notifyAll()方法才能執行,sleep()方法呼叫後執行緒經過一段時間會自動甦醒,wai()雖然也可以傳遞引數使其甦醒,可是wait會釋放鎖,所以甦醒後沒有獲得鎖就進入堵塞狀態,而sleep()甦醒後進入就緒狀態,但是如果碰到cpu不空閒,就會堵塞。

      1.4 為什麼wait(),notify(),notifyAll()要被定義到Object類中:看到一種很有意思的說法:對於多執行緒的競爭同一個物件資源,相當於多個男孩同時追一個女孩,女孩則是那個共享資源物件,而男孩則是執行緒。當某個男孩要約那個女孩吃飯看電影,這個女孩就需要告訴其他男孩那個時間段我沒有空,而不是約她的那個男孩告訴其他男孩這個訊息。就相當於共享資源物件實現了多個執行緒之間的通訊。在同步程式碼塊中,使用wait,notify來控制當前佔用資源的執行緒進入阻塞與喚醒進入就緒佇列,也就是說上述兩個方法實現了執行緒之間的通訊,用來通知執行緒的阻塞與喚醒。執行緒為了進入臨界區(也就是同步程式碼塊內)需要獲得鎖並等待鎖可用,他們並不知道也不需要知道哪些執行緒持有鎖,他們只需要知道當前資源是否被佔用是否可以獲得鎖,所以鎖的持有狀態應該由同步監視器來獲取而不是執行緒本身,因此wait(),notify()定義在Object類中而不是Thread中。

         1.5 如何停止一個正在執行的執行緒?使用stop方法終止(已經過期);使用interrupt方法終止執行緒;run()方法正常退出

  2、實現Runnable介面:

        1、定義子類實現Runnable介面;

        2、子類中重寫Runnable中的run()方法;

        3、通過Thread類含參構造器建立執行緒物件;

        4、將Runnable介面的子類物件作為實際引數傳遞給Thread含參構造器中;

        5、呼叫Thread類的start方法:開啟執行緒,呼叫Runnable子類介面的run()方法。

    2.1 上述兩種方式的區別:Thread類實現了Runnable介面

  3、實現Callable介面:可實現子執行緒結果傳回主執行緒這一功能(面試過程被問到過)

    3.1 兩種介面方式的區別:Runnable介面run()方法沒有返回值,Callable介面call()方法有返回值,是個泛型,和Future和Future Task配合使用獲取非同步執行結果。呼叫FutureTask.get()方法獲取返回值,但是這樣會造成主執行緒堵塞,不呼叫就不會堵塞。Runnable介面run()方法只能丟擲執行時異常,且無法捕獲;Callable介面中call方法允許丟擲異常且可以獲取異常資訊。

    3.2 什麼是FutureTask、Future、Callable?:Future可以拿到Callable被執行緒非同步執行後的返回值,表示非同步任務,是一個可能沒有完成的非同步任務結果。Callable用於產生結果,Future用於接收結果.FutureTask是Future的實現類,也是Runnable介面的實現類。

  4、Excecutors工具類建立執行緒池

 

三、如何保證多執行緒安全

  使用安全類,比如java.util.concurrent下的類,使用原子類Atomiclnteger;使用自動鎖,synchronized鎖,可以使用同步程式碼塊或同步方法;使用手動鎖,Lock lock=new ReentrantLock(),lock.lock(),lock.unlock();

四、什麼是執行緒安全?Servlet是執行緒安全的嗎?

  執行緒安全是指某個方法在多執行緒的環境下被呼叫時,能夠正確處理多執行緒之間的共享變數,使得程式能夠正常執行。Servlet不是執行緒安全的,它是單例項多執行緒,當多個執行緒同時訪問一個方法時,不能保證共享變數是安全的。SpringMVC的controller和Servlet一樣是是單例項多執行緒。如果既想提升效能又可以不用管理多個物件的話建議使用ThreadLocal來處理多執行緒。

五、對單例項物件執行緒安全的理解

  結合JVM記憶體機制,在一個執行緒中,如果建立一個物件,就會現在堆記憶體中開闢一個儲存空間,該物件在建立的時候就會在棧記憶體中開闢空間,呼叫其構造方法、靜態程式碼塊等,當方法結束會在棧記憶體中清除。一個執行緒在呼叫這個方法的時候會在棧記憶體中建立一個空間儲存物件方法。同理,對於一個執行緒載入單例模式物件的時候,他會在靜態共享域獲取單例物件,然後再呼叫物件方法(非靜態)的時候會在自己棧記憶體中開闢一個空間,說明如果多個執行緒處理同一個物件的時候如果不涉及到物件共有的屬性值,就不會存線上程安全問題。我們通常將資料連線層(dao)層設計成單例模式,因此當多個執行緒都想使用這個資料庫連線時會對效能有很大影響。如果多個縣茨城同時訪問一個物件的共有屬性時,需要對該資源加鎖。

六、為什麼ssh中的struts中action層必須建立多利?而ssm中的springmvc的controller層不需要建立多例?

  struts2中因為將前端獲取的值全部儲存在物件屬性中,所以肯定需要設定為多例;而springMVC中前端獲取的值直接進入方法中,所以設定為單例模式不會存線上程安全問題。

七、同步鎖機制

  Java中每個物件都有一個內建鎖,當程式執行到非靜態的synchronized同步方法上時,自動獲得與正在執行程式碼類的當前例項有關的鎖,獲得一個物件的鎖稱為獲取鎖、鎖定物件、在物件上鎖定或在物件上同步。當程式執行到synchronized同步方法或程式碼塊時該物件鎖才會起作用。一個物件只有一個鎖,所以一個執行緒獲得該鎖,就沒有其他執行緒可以獲得鎖,知道第一個執行緒釋放鎖,這也意味著在這期間任何其他執行緒都不能對該同步鎖內的資源進行操作。

只能同步方法而不能同步變數和類;要同步靜態方法則需一個用於整個類物件的鎖,這個物件就是這個類。