java的多線程安全,ReentrantLock與synchronized鎖

前言

多線程總的來講是一個很大的模塊,因此雖然以前就想寫但一直感受有地方沒有理解透,在通過了一段時間學習後,終於有點感受了,在此寫下隨筆。java

多線程安全問題##:

首先和你們討論一下多線程爲何會不安全,你們先看下面的程序。
/**
 - @author lw
  */
public class Test extends Thread{
    public void run()
    {   
        for(int i=1;i<=10;i++)
        {
            System.out.println(i);
        }
    }
    public static void main(String args[])
    {
        Test t1=new Test();
        Test t2=new Test();
        Test t3=new Test();
        Test t4=new Test();
        t1.start(); 
        t2.start();
        t3.start();
        t4.start();
    }

上面這段程序大體意思就是新建了四個線程,每一個線程的操做都是輸出1-10,按說來應該按線程啓動順序依次輸出,但其實並非。<--12345678112345678910234567891091012345678910-->這是輸出的結果。線程並無順序執行,緣由就是線程的搶佔。在線程一執行到一半,輸出到8的時候,便被其餘線程搶佔,其餘線程繼續輸出。緩存

這樣的併發會帶來什麼問題呢?你們請看下面這段代碼。安全

/**
 - @author lw
 -  */
public class Test extends Thread{
    static int temp=1;
    public void run()
    {   
        temp++;
        System.out.println(temp);
    }
    public static void main(String args[])
    {
        Test t1=new Test();
        Test t2=new Test();
        Test t3=new Test();
        Test t4=new Test();
        t1.start(); 
        t2.start();
        t3.start();
        t4.start();
    }
}

你們能夠上面的程序大體是定義了一個static的整形變量,而後每個線程能夠對這個變量加1,
首先static變量是全局共享的,每個線程都能操做這個變量,問題就出在這裏。若是有一次線程1運行,而後讀入了該變量爲1,這個時候線程2搶佔,而後對該變量進行了加一的操做,此時線程1再繼續運行,但該變量如今已是2了,線程1讀入的確是以前的1,在加一以後爲2,就出問題了。這種問題咱們稱爲線程之間不一樣步,由於線程之間的操做互相是不可見的。下面,咱們深刻討論一下爲何會這樣。多線程

線程不一樣步的緣由

線程之因此會不一樣步,本質緣由在於每一個線程的高速緩存區。每一個線程在建立後會有本身的一個緩存區,在線程要訪問主存中的變量的時候會先將主存中的變量加入緩存,而後進行操做,這樣能夠避免主存訪問過於頻繁,能夠加快線程的執行效率(相似於cache)。但問題在於每一個線程的緩存區之間不可見,若是載入的是主存中的同一個變量,分別進行了更改,就會出現線程不一樣步的問題。併發

不一樣步解決策略

好了,上面都是鋪墊,下面纔是重點,如何解決線程不一樣步問題。java給出了鎖的概念。所謂鎖形象一點理解就是一個線程在用一個資源就像一我的進了一扇門,若是不鎖門,其餘人也會進來,但若是加了鎖,就意味着這個資源被這個線程獨佔,並且必需要退出了才能被其餘線程使用。咱們經常使用的就是synchronized鎖,又叫同步鎖。咱們先看一下這個鎖的效果。異步

/**

 * @author lw

 *

 */

public class Test extends Thread{

    String lo="";
    public void run()

    {   

        synchronized(lo)

        {

            for(int i=1;i<=10;i++)

            {

                System.out.println(i);

            }

        }

    }

    public static void main(String args[])

    {

        Test t1=new Test();

        Test t2=new Test();

        Test t3=new Test();

        Test t4=new Test();

        t1.start(); 

        t2.start();

        t3.start()
        t4.start();

    }

}

在通過了上面的同步以後,線程即可以按順序運行,由於在第一個線程開始後,他會得到變量lo的鎖,而後執行下面的代碼塊,其餘線程在獲得這個鎖以前會處於一個阻塞狀態,等待第一個線程釋放鎖以後其餘線程競爭,而後得到鎖的線程繼續代碼塊裏的操做,這樣就能夠保證線程之間的異步了,接下來咱們須要知道的是爲何加了鎖能夠實現同步。學習

synchronized是如何實現同步的

好吧其實很簡單,比較機智的讀者可能已經猜到了,他實際上是使各個線程之間的高速緩存區失效了,而後線程要獲取該變量的時候須要在主存中讀寫,這個時候對該變量的操做對於各個線程之間是可見的,而後操做結束以後再刷新其緩存區,哈哈哈是否是很簡單。。。線程

synchronized需注意的事項

你們要注意的是synchronized加鎖的目標是對象,並非代碼塊。這是初學者容易進入的誤區。有人認爲只要是synchronized裏面的操做必定不會有問題, 但當這樣想的時候其實你已經涼了。請看下面的代碼。code

/**

 * @author lw

 *

 */

public class Test extends Thread{

    String lo=new String();

    public void run()

    {   

        synchronized(lo)

        {

            for(int i=1;i<=10;i++)

            {

                System.out.println(i);

            }

        }

    }

    public static void main(String args[])

    {

        Test t1=new Test();

        Test t2=new Test();

        Test t3=new Test();

        Test t4=new Test();

        t1.start(); 

        t2.start();

        t3.start();

        t4.start();

    }

}

看上去與上面的沒太大差異,但細心的讀者會發現有一行變成了String lo=new String();這個時候的鎖便沒有任何意義,由於這個對象每個線程都會new一個,也就是說每個線程都會得到一個,因此徹底不起做用。可能基礎欠佳的同窗會問以前的String lo=「」;爲何能夠,由於每個lo都會指向常量池(常量池這裏不展開講了 ,手要廢了。。不知道的能夠百度一下)中的同一個對象,因此每個線程的還都是指向同一段主存,鎖就會起做用。你們是否是以爲synchonized已經很完美了,no no no還有更完美的,rentrantlock閃亮登場!!!(打字好累啊。。。。#==)對象

reentrantlock

首先咱們討論一下synchonized的缺點。一是不靈活,synchonized在鎖定以後必需要代碼塊結束以後才能釋放鎖,而後被其餘線程得到。那麼若是獲取到鎖的這個線程要執行很是長的時間呢,那其餘的線程不是會一直阻塞在這裏,這時若是有哪一個線程生氣了不想等了怎麼辦?抱歉不能夠,須要一直等待。另外一方面,同步鎖的釋放順序也很固定,必須是加鎖的反順序,很不瀟灑等等。。。但咱們的reentrantlock就不同了,話很少說先看代碼。

/**
 * @author lw
 *
 */
public class Test extends Thread{
    private static ReentrantLock lock =new ReentrantLock();
    public void run()
    {   
        try{
            lock.lock();
            for(int i=1;i<=10;i++)
            {
                System.out.println(i);
            }
        }
        finally
        {
            lock.unlock();
        }
    }
    public static void main(String args[])
    {
        Test t1=new Test();
        Test t2=new Test();
        Test t3=new Test();
        Test t4=new Test();
        t1.start(); 
        t2.start();
        t3.start();
        t4.start();
    }
}

上面咱們能夠看到聲明瞭ReentrantLock對象後只需調用其中的lock方法即可直接加鎖,而釋放鎖須要unlock方法。這樣一是很靈活,不須要代碼塊結束再釋放,還有就是 ReentrantLock是可中斷的,若是等待的線程不想等了,好說,interrupt掉就行了,另外, ReentrantLock能夠設爲悲觀鎖和樂觀鎖,而synchonized則默認爲悲觀鎖,不可改變,不夠靈活。因此綜上,ReentrantLock更加靈活多變。但你們在使用時必定要記得unlock,最好寫在finally裏面防止忘記,否則就會形成其餘線程阻塞。

多線程是一個很大的知識塊,以上是筆者本身學習思考後的總結概括,還有不少沒有涉及到,另外分享內容若有不當之處望你們多多指正,共同進步~

下期預告

radius緩存

相關文章
相關標籤/搜索