JAVA多併發的本質

Android 併發

    通俗的解釋:一次做兩件事。


優點
    
    提高資源利用率:當一個任務沒有完全佔用系統資源,可以利用併發來提供資源利用率,同時也能更快的完成任務。


    程序更精簡: 提高效率,邏輯有清晰

    
    更好的相應程序:上傳圖片是,當界面還是正常運轉沒有卡死,圖片也能正常上傳,既保證界面被響應,有保證圖片可以上傳。

缺點
  
      開線程需要佔用更多資源
    
      設計併發框架不容易
    
      併發資源交互問題複雜
    
      子線程與主線程容易誤調
      
      結果與預期不符合

 

 併發的原理是多線程

 併發的控制核心是鎖。

 

鎖的方式有以下兩種:

        Java中每個對象都有一個鎖,並且是唯一的。


    Synchronized 的三種方式

        1.修飾實例的方法

                class Lock implements Runable{
                        
                    public Synchronized  void  a(){ }

                    public void run(){
                        a();
                    }
                    
                }

                

                Lock lock=new Lock();
            
                Thread t1=new Thread(lock);
                Thread t2=new Thread(lock);
    

                t1.start();
                t2.start();


                t1.join();
                t2.join();

                Synchronized   鎖是lock類對象,t1和t2用同一把鎖

                class Lock implements Runable{
                        
                    public Synchronized  void  a(){ }

                    public void run(){
                        a();
                    }
                    
                }

                

                Lock lock=new Lock();
                Lock lock2=new Lock();
            
                Thread t1=new Thread(lock);
                Thread t2=new Thread(lock2);
    

                t1.start();
                t2.start();


                t1.join();
                t2.join();

                Synchronized   鎖標記是lock類對象和lock2對象,t1和t2用的不是同一把鎖    
                如果需要使用同一把鎖,需要使用類鎖 參考2 一個類只有一個類鎖
                    
                            
        2.修飾靜態方法

            public static Synchronized  void  a(){ }


        3.修飾代碼塊

             ....

 

  

 線程join()方法

在很多情況下,主線程生成並起動了子線程,如果子線程裏要進行大量的耗時的運算,主線程往往將於子線程之前結束,但是如果主線程處理完其他的事務後,需要用到子線程的處理結果,也就是主線程需要等待
子線程執行完成之後再結束,這個時候就要用到join()方法了
 

 

Synchronized  原理

Object任何類的父類,存在wait()和notify()

            object.wait(); 線程沒有執行完畢,釋放鎖等待狀態

            object.notify();

Java是爲了更好的處理高併發,在object中定義wait()和notify()

 在JVM中創建對象,對象在堆內存中的表現形式見下圖


  

 

在JVM中創建對象,就會生成對象,對象頭32位/64位(jdk 32位或者64位) 如下圖:  hashcode class標識符  鎖標記    (備註:每個對象都
會只有一把鎖,存在對象的頭部)
 

鎖的本質: 就是定義了對象的內存信息 如下圖:

 

缺點:

            1)獲取鎖的線程執行完了該代碼塊,然後線程釋放對鎖的佔有;

          2)線程執行發生異常,此時JVM會讓線程自動釋放鎖。
    
            這個獲取鎖的線程由於要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,其他線程便只能乾巴巴地等待,試想一下,這多麼影響程序執行效率

            因此就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

              再舉個例子:當有多個線程讀寫文件時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。但是採用synchronized關鍵字來實現同步的
話,就會導致一個問題:如果多個線程都只是進行讀操作,所以當一個線程在進行讀操作時,其他線程只能等待無法進行讀操作。

              因此就需要一種機制來使得多個線程都只是進行讀操作時,線程之間不會發生衝突,通過Lock就可以辦到。

              另外,通過Lock可以知道線程有沒有成功獲取到鎖。這個是synchronized無法辦到的

 

 

Lock鎖

 

Lock 是接口

    1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;

  2)Lock和synchronized有一點非常大的不同,採用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的佔用;而Lock則必須要用戶去
手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

 

.Lock和synchronized的選擇:

  總結來說,Lock和synchronized有以下幾點不同:

  1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;

  2)synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放
鎖;

  3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;(I/O和Synchronized都能相應中斷,即不需要處理interruptionException異常

  4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。

  5)Lock可以提高多個線程進行讀操作的效率。

  在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。


最後鎖的方式

 

 1.可重入鎖

  如果鎖具備可重入性,則稱作爲可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明瞭鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線
程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。


   class MyClass{

    public synchronized void method1(){
        method2();
    }
    
    public synchronized void method2(){
    
    }
 }
      上述代碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A獲取了這個對象的鎖,而由於method2也是synchronized方法,假如synchronized不具備可
重入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因爲線程A已經持有了該對象的鎖,而又在申請獲取該對象的鎖,這樣就會線程A一直等待永遠不會獲取到的鎖。
而由於synchronized和Lock都具備可重入性,所以不會發生上述現象。

2.可中斷鎖


    可中斷鎖:顧名思義,就是可以相應中斷的鎖。

  在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

  如果某一線程A正在執行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。

  在前面演示lockInterruptibly()的用法時已經體現了Lock的可中斷性


3.公平鎖

    公平鎖即儘量以請求鎖的順序來獲取鎖。比如同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖。

  非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些線程永遠獲取不到鎖。

  在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。

  而對於ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置爲公平鎖。

4.讀寫鎖
 
   讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。

  正因爲有了讀寫鎖,才使得多個線程之間的讀操作不會發生衝突。

  ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實現了這個接口。

  可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章