亚洲欧美日韩综合系列在线_91精品人妻一区二区_欧美大肥婆一级特大AA片_九色91视频免费观看_亚洲综合国产精品_av中文字幕在线不卡_久久精品色综合网_看黄色视频的软件_无卡无码高清中文字幕码2024_亚洲欧美日韩天堂网

Java并發(fā)編程:自己動(dòng)手寫一把可重入鎖

來(lái)源:SpringChang 發(fā)布時(shí)間:2018-11-14 10:53:39 閱讀量:1112

關(guān)于線程安全的例子,我前面的文章Java并發(fā)編程:線程安全和ThreadLocal里面提到了,簡(jiǎn)而言之就是多個(gè)線程在同時(shí)訪問(wèn)或修改公共資源的時(shí)候,由于不同線程搶占公共資源而導(dǎo)致的結(jié)果不確定性,就是在并發(fā)編程中經(jīng)常要考慮的線程安全問(wèn)題。前面的做法是使用同步語(yǔ)句synchronized來(lái)隱式加鎖,現(xiàn)在我們嘗試來(lái)用Lock顯式加鎖來(lái)解決線程安全的問(wèn)題,先來(lái)看一下Lock接口的定義:


public interface Lock

1

Lock接口有幾個(gè)重要的方法:


//獲取鎖,如果鎖不可用,出于線程調(diào)度目的,將禁用當(dāng)前線程,并且在獲得鎖之前,該線程將一直處于休眠狀態(tài)。 

void lock()

//釋放鎖,

void unlock()

1

2

3

4

lock()和unlock()是Lock接口的兩個(gè)重要方法,下面的案例將會(huì)使用到它倆。Lock是一個(gè)接口,實(shí)現(xiàn)它的子類包括:可重入鎖:ReentrantLock, 讀寫鎖中的只讀鎖:ReentrantReadWriteLock.ReadLock和讀寫鎖中的只寫鎖:ReentrantReadWriteLock.WriteLock 。我們先來(lái)用一用ReentrantLock可重入鎖來(lái)解決線程安全問(wèn)題,如何還不明白什么是線程安全的同學(xué)可以回頭看我文章開頭給的鏈接文章。


import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;


public class MyThread implements Runnable {

    private int number = 5; //公共變量,5個(gè)線程都會(huì)訪問(wèn)和修改該變量


    private Lock lock = new ReentrantLock(); //可重入鎖


    @Override

    public void run() {

        lock.lock(); //進(jìn)方法的第一件事就是鎖住該方法,不能讓其他線程進(jìn)來(lái)

        try {

            number--;

            System.out.println("線程 : " + Thread.currentThread().getName() + "獲取到了公共資源,number = " + number);

            Thread.sleep((long)(Math.random()*1000));

        } catch (InterruptedException e) {

            e.printStackTrace();

        } finally {

            lock.unlock(); //釋放鎖

        }

    }


    public static void main(String[] args) {

        //起5個(gè)線程

        MyThread mt = new MyThread();

        Thread t1 = new Thread(mt, "t1");

        Thread t2 = new Thread(mt, "t2");

        Thread t3 = new Thread(mt, "t3");

        Thread t4 = new Thread(mt, "t4");

        Thread t5 = new Thread(mt, "t5");

        t1.start();

        t2.start();

        t3.start();

        t4.start();

        t5.start();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

控制臺(tái)輸出:


線程 : t1獲取到了公共資源,number = 4

線程 : t2獲取到了公共資源,number = 3

線程 : t3獲取到了公共資源,number = 2

線程 : t4獲取到了公共資源,number = 1

線程 : t5獲取到了公共資源,number = 0

1

2

3

4

5

程序中創(chuàng)建了一把鎖,一個(gè)公共變量的資源,和5個(gè)線程,每起一個(gè)線程就會(huì)對(duì)公共資源number做自減操作,從上面的輸出可以看到程序中的5個(gè)線程對(duì)number的操作得到正確的結(jié)果。需要注意的是,在你加鎖的代碼塊的finaly語(yǔ)句一定要釋放鎖,就是調(diào)用一下lock的unlock()方法。


現(xiàn)在來(lái)看一下什么是可重入鎖 ,可重入鎖就是同一個(gè)線程多次嘗試進(jìn)入同步代碼塊的時(shí)候,能夠順利的進(jìn)去并執(zhí)行。實(shí)例代碼如下:


import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;


public class MyThread implements Runnable {

    private int number = 5; //公共變量,5個(gè)線程都會(huì)訪問(wèn)和修改該變量


    private Lock lock = new ReentrantLock(); //可重入鎖


    public void sayHello(String threadName) {

        lock.lock();

        System.out.println("Hello!線程: " + threadName);

        lock.unlock();

    }


    @Override

    public void run() {

        lock.lock(); //進(jìn)方法的第一件事就是鎖住該方法,不能讓其他線程進(jìn)來(lái)

        try {

            number--;

            System.out.println("線程 : " + Thread.currentThread().getName() + "獲取到了公共資源,number = " + number);

            Thread.sleep((long)(Math.random()*1000));

            sayHello(Thread.currentThread().getName());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } finally {

            lock.unlock(); //釋放鎖

        }

    }


    public static void main(String[] args) {

        //起5個(gè)線程

        MyThread mt = new MyThread();

        Thread t1 = new Thread(mt, "t1");

        Thread t2 = new Thread(mt, "t2");

        Thread t3 = new Thread(mt, "t3");

        Thread t4 = new Thread(mt, "t4");

        Thread t5 = new Thread(mt, "t5");

        t1.start();

        t2.start();

        t3.start();

        t4.start();

        t5.start();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

上述代碼什么意思呢?意思是每起一個(gè)線程的時(shí)候,線程運(yùn)行run方法的時(shí)候,需要去調(diào)用sayHello()方法,那個(gè)sayHello()也是一個(gè)需要同步的和保證安全的方法,方法的第一行代碼一來(lái)就給方法上鎖,然后做完自己的工作之后再釋放鎖,工作期間,禁止其他線程進(jìn)來(lái),除了本線程除外。上面代碼輸出:


線程 : t1獲取到了公共資源,number = 4

Hello!線程: t1

線程 : t2獲取到了公共資源,number = 3

Hello!線程: t2

線程 : t3獲取到了公共資源,number = 2

Hello!線程: t3

線程 : t4獲取到了公共資源,number = 1

Hello!線程: t4

線程 : t5獲取到了公共資源,number = 0

Hello!線程: t5

1

2

3

4

5

6

7

8

9

10

實(shí)現(xiàn)一把簡(jiǎn)單的鎖

如果你明白了上面幾個(gè)例子是用來(lái)干嘛的,好,我們可以繼續(xù)進(jìn)行下去了,我們來(lái)實(shí)現(xiàn)一把最簡(jiǎn)單的鎖。先不考慮這把鎖的公平性和可重入性,只要求達(dá)到當(dāng)使用這把鎖的時(shí)候我們的代碼快安全即可。


我們先來(lái)定義自己的一把鎖MyLock。


public class MyLock implements Lock {

    

    @Override

    public void lock() {


    }


    @Override

    public void lockInterruptibly() throws InterruptedException {


    }


    @Override

    public boolean tryLock() {

        return false;

    }


    @Override

    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

        return false;

    }


    @Override

    public void unlock() {


    }


    @Override

    public Condition newCondition() {

        return null;

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

定義自己的鎖需要實(shí)現(xiàn)Lock接口,而上面是Lock接口需要實(shí)現(xiàn)的方法,我們拋開其他因素,只看lock()和unlock()方法。


public class MyLock implements Lock {


    private boolean isLocked = false; //定義一個(gè)變量,標(biāo)記鎖是否被使用


    @Override

    public synchronized void lock() {

        while(isLocked) { //不斷的重復(fù)判斷,isLocked是否被使用,如果已經(jīng)被占用,則讓新進(jìn)來(lái)想嘗試獲取鎖的線程等待,直到被正在運(yùn)行的線程喚醒

            try {

                wait();

            }catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        //進(jìn)入該代碼塊有兩種情況:

        // 1.第一個(gè)線程進(jìn)來(lái),此時(shí)isLocked變量的值為false,線程沒有進(jìn)入while循環(huán)體里面

        // 2.線程進(jìn)入那個(gè)循環(huán)體里面,調(diào)用了wait()方法并經(jīng)歷了等待階段,現(xiàn)在已經(jīng)被另一個(gè)線程喚醒,

        // 喚醒它的線程將那個(gè)變量isLocked設(shè)置為true,該線程才跳出了while循環(huán)體


        //跳出while循環(huán)體,本線程做的第一件事就是趕緊占用線程,并告訴其他線程說(shuō):嘿,哥們,我占用了,你必須等待

        isLocked = true; //將isLocked變量設(shè)置為true,表示本線程已經(jīng)占用

    }


    @Override

    public void lockInterruptibly() throws InterruptedException {


    }


    @Override

    public boolean tryLock() {

        return false;

    }


    @Override

    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

        return false;

    }


    @Override

    public synchronized void unlock() {

        //線程釋放鎖,釋放鎖的過(guò)程分為兩步

        //1. 將標(biāo)志變量設(shè)置為true,告訴其他線程,你可以占用了,不必死循環(huán)了

        //2. 喚醒正在等待中的線程,讓他們?nèi)?qiáng)制資源

        isLocked = false;

        notifyAll(); //通知所有等待的線程,誰(shuí)搶到我不管

    }


    @Override

    public Condition newCondition() {

        return null;

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

從上面代碼可以看到,這把鎖還是照樣用到了同步語(yǔ)句synchronized,只是同步的過(guò)程我們自己來(lái)實(shí)現(xiàn),用戶只需要調(diào)用我們的鎖上鎖和釋放鎖就行了。其核心思想是用一個(gè)公共變量isLocked來(lái)標(biāo)志當(dāng)前鎖是否被占用,如果被占用則當(dāng)前線程等待,然后每被喚醒一次就嘗試去搶那把鎖一次(處于等待狀態(tài)的線程不止當(dāng)前線程一個(gè)),這是lock方法里面使用那個(gè)while循環(huán)的原因。當(dāng)線程釋放鎖時(shí),首先將isLocked變量置為false,表示鎖沒有被占用,其實(shí)線程可以使用了,并調(diào)用notifyAll()方法喚醒正在等待的線程,至于誰(shuí)搶到我不管,不是本寶寶份內(nèi)的事。


那么上面我們實(shí)現(xiàn)的鎖是不是一把可重入的鎖呢?我們來(lái)調(diào)用sayHello()方法看看:


import java.util.concurrent.locks.Lock;


public class MyThread implements Runnable {

    private int number = 5; //公共變量,5個(gè)線程都會(huì)訪問(wèn)和修改該變量


    private Lock lock = new MyLock(); //創(chuàng)建一把自己的鎖


    public void sayHello(String threadName) {

        System.out.println(Thread.currentThread().getName() + "線程進(jìn)來(lái),需要占用鎖");

        lock.lock();

        System.out.println("Hello!線程: " + threadName);

        lock.unlock();

    }


    @Override

    public void run() {

        lock.lock(); //進(jìn)方法的第一件事就是鎖住該方法,不能讓其他線程進(jìn)來(lái)

        try {

            number--;

            System.out.println("線程 : " + Thread.currentThread().getName() + "獲取到了公共資源,number = " + number);

            Thread.sleep((long)(Math.random()*1000));

            sayHello(Thread.currentThread().getName());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } finally {

            lock.unlock(); //釋放鎖

        }

    }


    public static void main(String[] args) {

        //起5個(gè)線程

        MyThread mt = new MyThread();

        Thread t1 = new Thread(mt, "t1");

        Thread t2 = new Thread(mt, "t2");

        Thread t3 = new Thread(mt, "t3");

        Thread t4 = new Thread(mt, "t4");

        Thread t5 = new Thread(mt, "t5");

        t1.start();

        t2.start();

        t3.start();

        t4.start();

        t5.start();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

為了特意演示效果,我在sayHello方法加鎖之前打印一下當(dāng)前線程的名稱,現(xiàn)在控制臺(tái)輸出如下:


線程 : t1獲取到了公共資源,number = 4

t1線程進(jìn)來(lái),需要占用鎖

1

2

如上所述,t1線程啟動(dòng)并對(duì)公共變量做自減的時(shí)候,調(diào)用了sayHello方法。同一個(gè)線程t1,在線程啟動(dòng)的時(shí)候獲得過(guò)一次鎖,再在調(diào)用sayHello也想要獲取這把鎖,這樣的需求我們是可以理解的,畢竟sayHello方法也時(shí)候也需要達(dá)到線程安全效果嘛??蓡?wèn)題是痛一個(gè)線程嘗試獲取鎖兩次,程序就被卡住了,t1在run方法的時(shí)候獲得過(guò)鎖,在sayHello方法想再次獲得鎖的時(shí)候被告訴說(shuō):唉,哥們,該鎖被使用了,至于誰(shuí)在使用我不管(雖然正在使用該鎖線程就是我自己),你還是等等吧!所以導(dǎo)致結(jié)果就是sayHello處于等待狀態(tài),而run方法則等待sayHello執(zhí)行完??刂婆_(tái)則一直處于運(yùn)行狀態(tài)。


如果你不理解什么是可重入鎖和不可重入鎖,對(duì)比一下上面使用MyLock的例子和使用J.U.C.包下的ReentrantLock倆例子的區(qū)別,ReentrantLock是可重入的,而MyLock是不可重入的。


實(shí)現(xiàn)一把可重入鎖

現(xiàn)在我們來(lái)改裝一下這把鎖,讓他變成可重入鎖,也就是說(shuō):如果我已經(jīng)獲得了該鎖并且還沒釋放,我想再進(jìn)來(lái)幾次都行。核心思路是:用一個(gè)線程標(biāo)記變量記錄當(dāng)前正在執(zhí)行的線程,如果當(dāng)前想嘗試獲得鎖的線程等于正在執(zhí)行的線程,則獲取鎖成功。此外還需要用一個(gè)計(jì)數(shù)器來(lái)記錄一下本線程進(jìn)來(lái)過(guò)多少次,因?yàn)槿绻椒椒ㄕ{(diào)用unlock()時(shí),我不一定就要釋放鎖,只有本線程的所有加鎖方法都釋放鎖的時(shí)候我才真正的釋放鎖,計(jì)數(shù)器就起到這個(gè)功能。


改裝過(guò)后的代碼如下:


public class MyLock implements Lock {


    private boolean isLocked = false; //定義一個(gè)變量,標(biāo)記鎖是否被使用


    private Thread runningThread = null; //第一次線程進(jìn)來(lái)的時(shí)候,正在運(yùn)行的線程為null


    private int count = 0;  //計(jì)數(shù)器


    @Override

    public synchronized void lock() {

        Thread currentThread = Thread.currentThread();

        //不斷的重復(fù)判斷,isLocked是否被使用,如果已經(jīng)被占用,則讓新進(jìn)來(lái)想嘗試獲取鎖的線程等待,直到被正在運(yùn)行的線程喚醒

        //除了判斷當(dāng)前鎖是否被占用之外,還要判斷正在占用該鎖的是不是本線程自己

        while(isLocked && currentThread != runningThread) { //如果鎖已經(jīng)被占用,而占用者又是自己,則不進(jìn)入while循環(huán)

            try {

                wait();

            }catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        //進(jìn)入該代碼塊有三種情況:

        // 1.第一個(gè)線程進(jìn)來(lái),此時(shí)isLocked變量的值為false,線程沒有進(jìn)入while循環(huán)體里面

        // 2.線程進(jìn)入那個(gè)循環(huán)體里面,調(diào)用了wait()方法并經(jīng)歷了等待階段,現(xiàn)在已經(jīng)被另一個(gè)線程喚醒,

        // 3.線程不是第一次進(jìn)來(lái),但是新進(jìn)來(lái)的線程就是正在運(yùn)行的線程,則直接來(lái)到這個(gè)代碼塊

        // 喚醒它的線程將那個(gè)變量isLocked設(shè)置為true,該線程才跳出了while循環(huán)體


        //跳出while循環(huán)體,本線程做的第一件事就是趕緊占用線程,并告訴其他線程說(shuō):嘿,哥們,我占用了,你必須等待,計(jì)數(shù)器+1,并設(shè)置runningThread的值

        isLocked = true; //將isLocked變量設(shè)置為true,表示本線程已經(jīng)占用

        runningThread = currentThread; //給正在運(yùn)行的線程變量賦值

        count++; //計(jì)數(shù)器自增

    }


    @Override

    public void lockInterruptibly() throws InterruptedException {


    }


    @Override

    public boolean tryLock() {

        return false;

    }


    @Override

    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

        return false;

    }


    @Override

    public synchronized void unlock() {

        //線程釋放鎖,釋放鎖的過(guò)程分為三步

        //1. 判斷發(fā)出釋放鎖的請(qǐng)求是否是當(dāng)前線程

        //2. 判斷計(jì)數(shù)器是否歸零,也就是說(shuō),判斷本線程自己進(jìn)來(lái)了多少次,是不是全釋放鎖了

        //3. 還原標(biāo)志變量

        if(runningThread == Thread.currentThread()) {

            count--;//計(jì)數(shù)器自減

            if(count == 0) { //判斷是否歸零

                isLocked = false; //將鎖的狀態(tài)標(biāo)志為未占用

                runningThread = null;  //既然已經(jīng)真正釋放了鎖,正在運(yùn)行的線程則為null

                notifyAll(); //通知所有等待的線程,誰(shuí)搶到我不管

            }

        }

    }


    @Override

    public Condition newCondition() {

        return null;

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

如代碼注釋所述,這里新增了兩個(gè)變量runningThread和count,用于記錄當(dāng)前正在執(zhí)行的線程和當(dāng)前線程獲得鎖的次數(shù)。代碼的關(guān)鍵點(diǎn)在于while循環(huán)判斷測(cè)試獲得鎖的線程的條件,之前是只要鎖被占用就讓進(jìn)來(lái)的線程等待,現(xiàn)在的做法是,如果鎖已經(jīng)被占用,則判斷一下正在占用這把鎖的就是我自己,如果是,則獲得鎖,計(jì)數(shù)器+1;如果不是,則新進(jìn)來(lái)的線程進(jìn)入等待。相應(yīng)的,當(dāng)線程調(diào)用unlock()釋放鎖的時(shí)候,并不是立馬就釋放該鎖,而是判斷當(dāng)前線程還有沒有其他方法還在占用鎖,如果有,除了讓計(jì)數(shù)器減1之外什么事都別干,讓最后一個(gè)釋放鎖的方法來(lái)做最后的清除工作,當(dāng)計(jì)數(shù)器歸零時(shí),才表示真正的釋放鎖。


我知道你在懷疑這把被改造過(guò)后的鎖是不是能滿足我們的需求,現(xiàn)在就讓我們來(lái)運(yùn)行一下程序,控制臺(tái)輸出如下:


線程 : t1獲取到了公共資源,number = 4

t1線程進(jìn)來(lái),需要占用鎖

Hello!線程: t1

線程 : t5獲取到了公共資源,number = 3

t5線程進(jìn)來(lái),需要占用鎖

Hello!線程: t5

線程 : t2獲取到了公共資源,number = 2

t2線程進(jìn)來(lái),需要占用鎖

Hello!線程: t2

線程 : t4獲取到了公共資源,number = 1

t4線程進(jìn)來(lái),需要占用鎖

Hello!線程: t4

線程 : t3獲取到了公共資源,number = 0

t3線程進(jìn)來(lái),需要占用鎖

Hello!線程: t3

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

嗯,沒錯(cuò),這就是我們想要的結(jié)果。


好了,自己動(dòng)手寫一把可重入鎖就先寫到這了,后面有時(shí)間再寫一篇用AQS實(shí)現(xiàn)的可重入鎖,畢竟ReentrantLock這哥們就是用AQS實(shí)現(xiàn)的可重入鎖,至于什么是AQS以及如何用AQS實(shí)現(xiàn)一把可重入鎖,且聽我慢慢道來(lái)。如果你看懂這篇文章的思路或者如果是你看完了這篇文章有動(dòng)手寫一把可重入鎖的沖動(dòng),麻煩點(diǎn)個(gè)贊哦,畢竟大半夜的寫文章挺累的,是吧?

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



標(biāo)簽: 環(huán)境搭建
分享:
評(píng)論:
你還沒有登錄,請(qǐng)先