1. 程式人生 > 實用技巧 >Java多執行緒同步和非同步問題

Java多執行緒同步和非同步問題

我們首先來說一下多執行緒:

多執行緒很形象的例子就是:在一個時刻下,一個班級的學生有人在拖地,有人在擦窗戶,有人在擦桌子

按照單執行緒程式,肯定是先去拖地,再去擦窗戶,再去擦桌子。但是在多執行緒就好像他們在一個時間點同時發生了。

為什麼要說好像?是因為在單核系統下,CPU不可能同時進行兩個事件。它只是完成這個事件之後迅速切換到另外一個事件而造成兩個事件好像是同時發生的一樣的假象。

接下來說一下怎麼寫多執行緒程式:

寫多執行緒程式有兩種方法:

1、繼承於Thread類(這個類就是實現了Runnable介面)

2、實現Runnable介面

兩者區別不太大

1、下面給出繼承Thread實現多執行緒程式示例:

使用這種方法你只需要覆蓋重寫Thread的run()方法就可以,run方法裡面就寫你要多執行緒執行的程式就可以

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //3.建立執行緒物件,例項化呼叫
        Tuodi t=new Tuodi("張三");
        //4. 啟動執行緒
        t.start();
        new Chazuozi("李四").start();  //匿名呼叫
        
new Chachuanghu("王五").start(); } } //1. 繼承java.lang.Thread 類 //2. 執行緒體run 實現執行緒功能 class Tuodi extends Thread{ String name; public Tuodi(String name) { super(); this.name = name; } @Override //執行緒體 實現執行緒功能 public void run() { int len=100; while
(len>0) { len-=1; System.out.println(name+"在拖地!"); } } } class Chazuozi extends Thread{ String name; public Chazuozi(String name) { super(); this.name = name; } @Override //執行緒體 實現執行緒功能 public void run() { int len=100; while(len>0) { len-=1; System.out.println(name+"在擦桌子!"); } } } class Chachuanghu extends Thread{ String name; public Chachuanghu(String name) { super(); this.name = name; } @Override //執行緒體 實現執行緒功能 public void run() { int len=100; while(len>0) { len-=1; System.out.println(name+"在擦窗戶!"); } } }

我們來用一個多執行緒賣票程式來說一下多執行緒的同步和非同步

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new Thread(new SailWindow(),"視窗一").start();
        new Thread(new SailWindow(),"視窗二").start();
        new Thread(new SailWindow(),"視窗三").start();
        new Thread(new SailWindow(),"視窗四").start();
    }
}

class SailWindow implements Runnable{
    static int tickets=100;
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        
        while(tickets>0) {
            System.out.println(Thread.currentThread().getName()+"正在賣出第"+tickets--+"張票!");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

輸出:

  1 視窗二正在賣出第100張票!
  2 視窗四正在賣出第97張票!
  3 視窗三正在賣出第98張票!
  4 視窗一正在賣出第99張票!
  5 視窗四正在賣出第96張票!
  6 視窗二正在賣出第94張票!
  7 視窗三正在賣出第95張票!
  8 視窗一正在賣出第93張票!
  9 視窗四正在賣出第92張票!
 10 視窗三正在賣出第91張票!
 11 視窗二正在賣出第90張票!
 12 視窗一正在賣出第89張票!
 13 視窗四正在賣出第88張票!
 14 視窗二正在賣出第87張票!
 15 視窗三正在賣出第87張票!
 16 視窗一正在賣出第86張票!
 17 視窗二正在賣出第85張票!
 18 視窗四正在賣出第84張票!
 19 視窗三正在賣出第83張票!
 20 視窗二正在賣出第82張票!
 21 視窗四正在賣出第80張票!
 22 視窗三正在賣出第81張票!
 23 視窗一正在賣出第79張票!
 24 視窗二正在賣出第78張票!
 25 視窗三正在賣出第77張票!
 26 視窗四正在賣出第76張票!
 27 視窗一正在賣出第75張票!
 28 視窗二正在賣出第74張票!
 29 視窗四正在賣出第71張票!
 30 視窗一正在賣出第72張票!
 31 視窗三正在賣出第73張票!
 32 視窗二正在賣出第70張票!
 33 視窗一正在賣出第70張票!
 34 視窗四正在賣出第70張票!
 35 視窗三正在賣出第70張票!
 36 視窗二正在賣出第69張票!
 37 視窗四正在賣出第66張票!
 38 視窗三正在賣出第67張票!
 39 視窗一正在賣出第68張票!
 40 視窗三正在賣出第64張票!
 41 視窗二正在賣出第65張票!
 42 視窗一正在賣出第65張票!
 43 視窗四正在賣出第65張票!
 44 視窗三正在賣出第63張票!
 45 視窗二正在賣出第61張票!
 46 視窗四正在賣出第63張票!
 47 視窗一正在賣出第62張票!
 48 視窗三正在賣出第60張票!
 49 視窗四正在賣出第58張票!
 50 視窗一正在賣出第58張票!
 51 視窗二正在賣出第59張票!
 52 視窗四正在賣出第57張票!
 53 視窗一正在賣出第55張票!
 54 視窗二正在賣出第55張票!
 55 視窗三正在賣出第56張票!
 56 視窗四正在賣出第54張票!
 57 視窗三正在賣出第52張票!
 58 視窗二正在賣出第52張票!
 59 視窗一正在賣出第53張票!
 60 視窗三正在賣出第51張票!
 61 視窗一正在賣出第50張票!
 62 視窗二正在賣出第49張票!
 63 視窗四正在賣出第50張票!
 64 視窗四正在賣出第48張票!
 65 視窗二正在賣出第46張票!
 66 視窗一正在賣出第45張票!
 67 視窗三正在賣出第47張票!
 68 視窗四正在賣出第44張票!
 69 視窗一正在賣出第42張票!
 70 視窗三正在賣出第42張票!
 71 視窗二正在賣出第43張票!
 72 視窗四正在賣出第41張票!
 73 視窗一正在賣出第38張票!
 74 視窗三正在賣出第39張票!
 75 視窗二正在賣出第40張票!
 76 視窗三正在賣出第37張票!
 77 視窗二正在賣出第35張票!
 78 視窗一正在賣出第35張票!
 79 視窗四正在賣出第36張票!
 80 視窗四正在賣出第34張票!
 81 視窗一正在賣出第32張票!
 82 視窗二正在賣出第31張票!
 83 視窗三正在賣出第33張票!
 84 視窗四正在賣出第30張票!
 85 視窗一正在賣出第29張票!
 86 視窗二正在賣出第28張票!
 87 視窗三正在賣出第28張票!
 88 視窗四正在賣出第27張票!
 89 視窗三正在賣出第26張票!
 90 視窗一正在賣出第26張票!
 91 視窗二正在賣出第25張票!
 92 視窗四正在賣出第24張票!
 93 視窗二正在賣出第22張票!
 94 視窗三正在賣出第23張票!
 95 視窗一正在賣出第22張票!
 96 視窗一正在賣出第21張票!
 97 視窗四正在賣出第20張票!
 98 視窗三正在賣出第19張票!
 99 視窗二正在賣出第19張票!
100 視窗一正在賣出第18張票!
101 視窗三正在賣出第17張票!
102 視窗二正在賣出第16張票!
103 視窗四正在賣出第16張票!
104 視窗四正在賣出第15張票!
105 視窗二正在賣出第12張票!
106 視窗一正在賣出第14張票!
107 視窗三正在賣出第13張票!
108 視窗一正在賣出第11張票!
109 視窗二正在賣出第10張票!
110 視窗三正在賣出第10張票!
111 視窗四正在賣出第11張票!
112 視窗四正在賣出第9張票!
113 視窗三正在賣出第6張票!
114 視窗一正在賣出第8張票!
115 視窗二正在賣出第7張票!
116 視窗三正在賣出第5張票!
117 視窗二正在賣出第3張票!
118 視窗一正在賣出第3張票!
119 視窗四正在賣出第4張票!
120 視窗四正在賣出第2張票!
121 視窗三正在賣出第0張票!
122 視窗一正在賣出第-1張票!
123 視窗二正在賣出第1張票!
View Code

你會發現輸出的結果竟然還有賣出第-1張票,這就是沒有進行同步處理引發的。

因為tickets是一個類變數,所以所有程序都共用這一個變數。

有可能有好多執行緒訪問tickets的值的時候tickets大於0,但是這個時候可能對於一個執行緒來說它還沒有對tickets-=1,也就是還沒有更新tickets的值。這個時候就會發生上面的問題。

解決同步問題可以使用關鍵字synchronized或者是java.util.concurrent.locks的lock來實現同步訪問。

下面給出使用關鍵字synchronized同步訪問的示例:

public class Main {

//非同步
    static void method(Thread thread) {
        System.out.println("begin " + thread.getName());
        try {
            Thread.sleep(2000);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        System.out.println("end " + thread.getName());
    }

//同步方式一:同步方法
    synchronized static void method1(Thread thread) {// 這個方法是同步的方法,每次只有一
//個執行緒可以進來
        System.out.println("begin " + thread.getName());
        try {
            Thread.sleep(2000);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        System.out.println("end " + thread.getName());
    }

//同步方式二:同步程式碼塊
    static void method2(Thread thread) {
        synchronized (Main.class) {
            System.out.println("begin " + thread.getName());
            try {
                Thread.sleep(2000);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println("end " + thread.getName());
        }
    }

//同步方式三:使用同步物件鎖
    private static Object _lock1 = new Object();
    private static byte _lock2[] = {};// 據說,此鎖更可提高效能。源於:鎖的物件越小越好

    static void method3(Thread thread) {
        synchronized (_lock1) {
            System.out.println("begin " + thread.getName());
            try {
                Thread.sleep(2000);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println("end " + thread.getName());
        }
    }

    public static void main(String[] args) {
//啟動3個執行緒,這裡用了匿名類
        for (int i = 0; i < 3; i++) {
            new Thread() {
                public void run() {
                    method(this);
//method1(this);
//method2(this);
//method3(this);
                }
            }.start();
        }
    }
}

通過java.util.concurrent.locks的lock來實現同步訪問(原文連結:https://www.cnblogs.com/dolphin0520/p/3923167.html)

下面內容為轉載--------------------------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------------------------------------------------------------------

在上一篇文章中我們講到了如何使用關鍵字synchronized來實現同步訪問。本文我們繼續來探討這個問題,從Java 5之後,在java.util.concurrent.locks包下提供了另外一種方式來實現同步訪問,那就是Lock。

  也許有朋友會問,既然都可以通過synchronized來實現同步訪問了,那麼為什麼還需要提供Lock?這個問題將在下面進行闡述。本文先從synchronized的缺陷講起,然後再講述java.util.concurrent.locks包下常用的有哪些類和介面,最後討論以下一些關於鎖的概念方面的東西

  以下是本文目錄大綱:

  一.synchronized的缺陷

  二.java.util.concurrent.locks包下常用的類

  三.鎖的相關概念介紹

  若有不正之處請多多諒解,並歡迎批評指正。

  請尊重作者勞動成果,轉載請標明原文連結:

  http://www.cnblogs.com/dolphin0520/p/3923167.html

一.synchronized的缺陷

  synchronized是java中的一個關鍵字,也就是說是Java語言內建的特性。那麼為什麼會出現Lock呢?

  在上面一篇文章中,我們瞭解到如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況:

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

  2)執行緒執行發生異常,此時JVM會讓執行緒自動釋放鎖。

  那麼如果這個獲取鎖的執行緒由於要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能乾巴巴地等待,試想一下,這多麼影響程式執行效率。

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

  再舉個例子:當有多個執行緒讀寫檔案時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。

  但是採用synchronized關鍵字來實現同步的話,就會導致一個問題:

  如果多個執行緒都只是進行讀操作,所以當一個執行緒在進行讀操作時,其他執行緒只能等待無法進行讀操作。

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

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

  總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:

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

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

二.java.util.concurrent.locks包下常用的類

  下面我們就來探討一下java.util.concurrent.locks包中常用的類和介面。

  1.Lock

  首先要說明的就是Lock,通過檢視Lock的原始碼可知,Lock是一個介面:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

下面來逐個講述Lock介面中每個方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來獲取鎖的。unLock()方法是用來釋放鎖的。newCondition()這個方法暫且不在此講述,會在後面的執行緒協作一文中講述。

  在Lock中聲明瞭四個方法來獲取鎖,那麼這四個方法有何區別呢?

  首先lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行等待。

  由於在前面講到如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:

Lock lock = ...;
lock.lock();
try{
    //處理任務
}catch(Exception ex){
     
}finally{
    lock.unlock();   //釋放鎖
}

  tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

  tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

  所以,一般情況下通過tryLock來獲取鎖時是這樣使用的:

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //處理任務
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //釋放鎖
     } 
}else {
    //如果不能獲取鎖,則直接做其他事情
}

lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果執行緒正在等待獲取鎖,則這個執行緒能夠響應中斷,即中斷執行緒的等待狀態。也就使說,當兩個執行緒同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時執行緒A獲取到了鎖,而執行緒B只有在等待,那麼對執行緒B呼叫threadB.interrupt()方法能夠中斷執行緒B的等待過程。

  由於lockInterruptibly()的宣告中丟擲了異常,所以lock.lockInterruptibly()必須放在try塊中或者在呼叫lockInterruptibly()的方法外宣告丟擲InterruptedException。

  因此lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

注意,當一個執行緒獲取了鎖之後,是不會被interrupt()方法中斷的。因為本身在前面的文章中講過單獨呼叫interrupt()方法不能中斷正在執行過程中的執行緒,只能中斷阻塞過程中的執行緒。

  因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。

  而用synchronized修飾的話,當一個執行緒處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

  2.ReentrantLock

  ReentrantLock,意思是“可重入鎖”,關於可重入鎖的概念在下一節講述。ReentrantLock是唯一實現了Lock介面的類,並且ReentrantLock提供了更多的方法。下面通過一些例項看具體看一下如何使用ReentrantLock。

  例子1,lock()的正確使用方法

public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        Lock lock = new ReentrantLock();    //注意這個地方
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了鎖");
            for(int i=0;i<5;i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(thread.getName()+"釋放了鎖");
            lock.unlock();
        }
    }
}

輸出:

Thread-0得到了鎖
Thread-1得到了鎖
Thread-0釋放了鎖
Thread-1釋放了鎖

也許有朋友會問,怎麼會輸出這個結果?第二個執行緒怎麼會在第一個執行緒釋放鎖之前得到了鎖?原因在於,在insert方法中的lock變數是區域性變數,每個執行緒執行該方法時都會儲存一個副本,那麼理所當然每個執行緒執行到lock.lock()處獲取的是不同的鎖,所以就不會發生衝突。

  知道了原因改起來就比較容易了,只需要將lock宣告為類的屬性即可。

public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意這個地方
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了鎖");
            for(int i=0;i<5;i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(thread.getName()+"釋放了鎖");
            lock.unlock();
        }
    }
}

這樣就是正確地使用Lock的方法了。

  例子2,tryLock()的使用方法

public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意這個地方
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        if(lock.tryLock()) {
            try {
                System.out.println(thread.getName()+"得到了鎖");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"釋放了鎖");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName()+"獲取鎖失敗");
        }
    }
}

輸出:

Thread-0得到了鎖
Thread-1獲取鎖失敗
Thread-0釋放了鎖

例子3,lockInterruptibly()響應中斷的使用方法:

public class Test {
    private Lock lock = new ReentrantLock();   
    public static void main(String[] args)  {
        Test test = new Test();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        thread2.start();
         
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }  
     
    public void insert(Thread thread) throws InterruptedException{
        lock.lockInterruptibly();   //注意,如果需要正確中斷等待鎖的執行緒,必須將獲取鎖放在外面,然後將InterruptedException丟擲
        try {  
            System.out.println(thread.getName()+"得到了鎖");
            long startTime = System.currentTimeMillis();
            for(    ;     ;) {
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
                //插入資料
            }
        }
        finally {
            System.out.println(Thread.currentThread().getName()+"執行finally");
            lock.unlock();
            System.out.println(thread.getName()+"釋放了鎖");
        }  
    }
}
 
class MyThread extends Thread {
    private Test test = null;
    public MyThread(Test test) {
        this.test = test;
    }
    @Override
    public void run() {
         
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中斷");
        }
    }
}

執行之後,發現thread2能夠被正確中斷。

  3.ReadWriteLock

  ReadWriteLock也是一個介面,在它裡面只定義了兩個方法:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}

  一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將檔案的讀寫操作分開,分成2個鎖來分配給執行緒,從而使得多個執行緒可以同時進行讀操作。下面的ReentrantReadWriteLock實現了ReadWriteLock介面。

  4.ReentrantReadWriteLock

  ReentrantReadWriteLock裡面提供了很多豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖。

  下面通過幾個例子來看一下ReentrantReadWriteLock具體用法。

  假如有多個執行緒要同時進行讀操作的話,先看一下synchronized達到的效果:

public class Test {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
    }  
     
    public synchronized void get(Thread thread) {
        long start = System.currentTimeMillis();
        while(System.currentTimeMillis() - start <= 1) {
            System.out.println(thread.getName()+"正在進行讀操作");
        }
        System.out.println(thread.getName()+"讀操作完畢");
    }
}

這段程式的輸出結果會是,直到thread1執行完讀操作之後,才會列印thread2執行讀操作的資訊。

 1 Thread-0正在進行讀操作
 2 Thread-0正在進行讀操作
 3 Thread-0正在進行讀操作
 4 Thread-0正在進行讀操作
 5 Thread-0正在進行讀操作
 6 Thread-0正在進行讀操作
 7 Thread-0正在進行讀操作
 8 Thread-0正在進行讀操作
 9 Thread-0正在進行讀操作
10 Thread-0正在進行讀操作
11 Thread-0正在進行讀操作
12 Thread-0正在進行讀操作
13 Thread-0正在進行讀操作
14 Thread-0正在進行讀操作
15 Thread-0正在進行讀操作
16 Thread-0正在進行讀操作
17 Thread-0正在進行讀操作
18 Thread-0正在進行讀操作
19 Thread-0正在進行讀操作
20 Thread-0正在進行讀操作
21 Thread-0正在進行讀操作
22 Thread-0正在進行讀操作
23 Thread-0正在進行讀操作
24 Thread-0正在進行讀操作
25 Thread-0正在進行讀操作
26 Thread-0正在進行讀操作
27 Thread-0正在進行讀操作
28 Thread-0正在進行讀操作
29 Thread-0讀操作完畢
30 Thread-1正在進行讀操作
31 Thread-1正在進行讀操作
32 Thread-1正在進行讀操作
33 Thread-1正在進行讀操作
34 Thread-1正在進行讀操作
35 Thread-1正在進行讀操作
36 Thread-1正在進行讀操作
37 Thread-1正在進行讀操作
38 Thread-1正在進行讀操作
39 Thread-1正在進行讀操作
40 Thread-1正在進行讀操作
41 Thread-1正在進行讀操作
42 Thread-1正在進行讀操作
43 Thread-1正在進行讀操作
44 Thread-1正在進行讀操作
45 Thread-1正在進行讀操作
46 Thread-1正在進行讀操作
47 Thread-1正在進行讀操作
48 Thread-1正在進行讀操作
49 Thread-1正在進行讀操作
50 Thread-1正在進行讀操作
51 Thread-1正在進行讀操作
52 Thread-1正在進行讀操作
53 Thread-1正在進行讀操作
54 Thread-1正在進行讀操作
55 Thread-1正在進行讀操作
56 Thread-1正在進行讀操作
57 Thread-1正在進行讀操作
58 Thread-1正在進行讀操作
59 Thread-1正在進行讀操作
60 Thread-1正在進行讀操作
61 Thread-1正在進行讀操作
62 Thread-1正在進行讀操作
63 Thread-1正在進行讀操作
64 Thread-1正在進行讀操作
65 Thread-1正在進行讀操作
66 Thread-1正在進行讀操作
67 Thread-1正在進行讀操作
68 Thread-1正在進行讀操作
69 Thread-1正在進行讀操作
70 Thread-1正在進行讀操作
71 Thread-1正在進行讀操作
72 Thread-1正在進行讀操作
73 Thread-1讀操作完畢
View Code

而改成用讀寫鎖的話:

public class Test {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
    }  
     
    public void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
             
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在進行讀操作");
            }
            System.out.println(thread.getName()+"讀操作完畢");
        } finally {
            rwl.readLock().unlock();
        }
    }
}

輸出:

 1 Thread-0正在進行讀操作
 2 Thread-0正在進行讀操作
 3 Thread-1正在進行讀操作
 4 Thread-0正在進行讀操作
 5 Thread-1正在進行讀操作
 6 Thread-0正在進行讀操作
 7 Thread-1正在進行讀操作
 8 Thread-1正在進行讀操作
 9 Thread-1正在進行讀操作
10 Thread-1正在進行讀操作
11 Thread-1正在進行讀操作
12 Thread-1正在進行讀操作
13 Thread-0正在進行讀操作
14 Thread-0正在進行讀操作
15 Thread-0正在進行讀操作
16 Thread-0正在進行讀操作
17 Thread-1正在進行讀操作
18 Thread-1正在進行讀操作
19 Thread-1正在進行讀操作
20 Thread-1正在進行讀操作
21 Thread-0正在進行讀操作
22 Thread-1正在進行讀操作
23 Thread-1正在進行讀操作
24 Thread-0正在進行讀操作
25 Thread-1正在進行讀操作
26 Thread-1正在進行讀操作
27 Thread-0正在進行讀操作
28 Thread-1正在進行讀操作
29 Thread-1正在進行讀操作
30 Thread-1正在進行讀操作
31 Thread-0正在進行讀操作
32 Thread-1正在進行讀操作
33 Thread-1正在進行讀操作
34 Thread-0正在進行讀操作
35 Thread-1正在進行讀操作
36 Thread-0正在進行讀操作
37 Thread-1正在進行讀操作
38 Thread-0正在進行讀操作
39 Thread-1正在進行讀操作
40 Thread-0正在進行讀操作
41 Thread-1正在進行讀操作
42 Thread-0正在進行讀操作
43 Thread-1正在進行讀操作
44 Thread-0正在進行讀操作
45 Thread-1正在進行讀操作
46 Thread-0正在進行讀操作
47 Thread-1正在進行讀操作
48 Thread-0讀操作完畢
49 Thread-1讀操作完畢
View Code

說明thread1和thread2在同時進行讀操作。

  這樣就大大提升了讀操作的效率。

  不過要注意的是,如果有一個執行緒已經佔用了讀鎖,則此時其他執行緒如果要申請寫鎖,則申請寫鎖的執行緒會一直等待釋放讀鎖。

  如果有一個執行緒已經佔用了寫鎖,則此時其他執行緒如果申請寫鎖或者讀鎖,則申請的執行緒會一直等待釋放寫鎖。

  關於ReentrantReadWriteLock類中的其他方法感興趣的朋友可以自行查閱API文件。

  5.Lock和synchronized的選擇

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

  1)Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;

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

  3)Lock可以讓等待鎖的執行緒響應中斷,而synchronized卻不行,使用synchronized時,等待的執行緒會一直等待下去,不能夠響應中斷;

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

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

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

三.鎖的相關概念介紹

  在前面介紹了Lock的基本使用,這一節來介紹一下與鎖相關的幾個概念。

  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,它預設情況下是非公平鎖,但是可以設定為公平鎖。

  看一下這2個類的原始碼就清楚了:

  在ReentrantLock中定義了2個靜態內部類,一個是NotFairSync,一個是FairSync,分別用來實現非公平鎖和公平鎖。

  我們可以在建立ReentrantLock物件時,通過以下方式來設定鎖的公平性:

ReentrantLock lock = new ReentrantLock(true);

如果引數為true表示為公平鎖,為fasle為非公平鎖。預設情況下,如果使用無參構造器,則是非公平鎖。

另外在ReentrantLock類中定義了很多方法,比如:

  isFair() //判斷鎖是否是公平鎖

  isLocked() //判斷鎖是否被任何執行緒獲取了

  isHeldByCurrentThread() //判斷鎖是否被當前執行緒獲取了

  hasQueuedThreads() //判斷是否有執行緒在等待該鎖

  在ReentrantReadWriteLock中也有類似的方法,同樣也可以設定為公平鎖和非公平鎖。不過要記住,ReentrantReadWriteLock並未實現Lock介面,它實現的是ReadWriteLock介面。

  4.讀寫鎖

  讀寫鎖將對一個資源(比如檔案)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。

  正因為有了讀寫鎖,才使得多個執行緒之間的讀操作不會發生衝突。

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

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

  上面已經演示過了讀寫鎖的使用方法,在此不再贅述。

  參考資料:

  http://blog.csdn.net/ns_code/article/details/17487337

  http://houlinyan.iteye.com/blog/1112535

  http://ifeve.com/locks/

  http://ifeve.com/read-write-locks/

  http://blog.csdn.net/fancyerii/article/details/6783224

  http://blog.csdn.net/ghsau/article/details/7461369/

  http://blog.csdn.net/zhaozhenzuo/article/details/37109015