java semaphore lock_Java并发编程:Semaphore和Lock区别

Java提供了一个类Semaphore来实现信号量,概念上讲,一个信号量相当于持有一些许可(permits),线程可以调用Semaphore对象的acquire()方法获取一个许可,调用release()来归还一个许可

1 构造方法:

Semaphore有两个构造方法 Semaphore(int)、Semaphore(int,boolean),参数中的int表示该信号量拥有的许可数量,boolean表示获取许可的时候是否是公平的,如果是公平的那么,当有多个线程要获取许可时,会按照线程来的先后顺序分配许可,否则,线程获得许可的顺序是不定的。这里在jdk中讲到 “一般而言,非公平时候的吞吐量要高于公平锁”,这是为什么呢?附上链接中的一段话:

非公平锁性能高于公平锁性能的原因:在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。

2 获取许可

可以使用acquire()、acquire(int)、tryAcquire()等去获取许可,其中int参数表示一次性要获取几个许可,默认为1个,acquire方法在没有许可的情况下,要获取许可的线程会阻塞,而tryAcquire()方法在没有许可的情况下会立即返回 false,要获取许可的线程不会阻塞,这与Lock类的lock()与tryLock()类似

3 释放许可

线程可调用 release()、release(int)来释放(归还)许可,注意一个线程调用release()之前并不要求一定要调用了acquire (There is no requirement that a thread that releases a permit must have acquired that permit by calling {@link #acquire})

4 使用场景

我们一般使用信号量来限制访问资源的线程数量,比如有一个食堂,最多允许5个人同时吃饭,则如下:

classEatThreadextendsThread{

private Semaphore semaphore;

publicEatThread(Semaphore semaphore){

this.semaphore=semaphore;

}

publicvoidrun(){

try {

semaphore.acquire();//获取一个许可,当然也可以调用acquire(int),这样一个线程就能拿到多个许可

long eatTime=(long) (Math.random()*10);

System.out.println(Thread.currentThread().getId()+" 正在吃饭");

TimeUnit.SECONDS.sleep(eatTime);

System.out.println(Thread.currentThread().getId()+" 已经吃完");

semaphore.release();//归还许可

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public classSemaphoreTest{

publicstaticvoidmain(String[] args){

Semaphore semaphore=new Semaphore(5);//总共有5个许可

for(int i=0;i<7;i++){//定义七个吃的线程

new EatThread(semaphore).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

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

结果如下:

9 正在吃饭

15 正在吃饭

13 正在吃饭

13 已经吃完

11 正在吃饭

10 正在吃饭

10 已经吃完

12 正在吃饭

14 正在吃饭

11 已经吃完

15 已经吃完

14 已经吃完

9 已经吃完

12 已经吃完

1

2

3

4

5

6

7

8

9

10

11

12

13

14

1

2

3

4

5

6

7

8

9

10

11

12

13

14

当我们在构造Semaphore对象时,如果设置的许可数量为1,这时便会达到一个互斥排他锁的效果,只有一个许可,有一个线程获取了这个许可,那么其他线程只有等待这个线程归还了许可才能获取到许可,当将Semaphore用作互斥排他锁的作用时,要注意:

A semaphore initialized to one, and which is used such that it only has at most one permit available, can serve as a mutual exclusion lock. This is more commonly known as a binary semaphore, because it only has two states: one permit available, or zero permits available. When used in this way, the binary semaphore has the property (unlike many Lock implementations), that the “lock” can be released by a thread other than the owner (as semaphores have no notion of ownership). This can be useful in some specialized contexts, such as deadlock recovery.

文档中提到,Semaphore与jdk中的Lock的区别是

1. 使用Lock.unlock()之前,该线程必须事先持有这个锁(通过Lock.lock()获取),如下:

public class LockTest {

publicstaticvoidmain(String[] args) {

Lock lock=new ReentrantLock();

lock.unlock();

}

}

1

2

3

4

5

6

1

2

3

4

5

6

则会抛出异常,因为该线程事先并没有获取lock对象的锁:

Exception in thread "main" java.lang.IllegalMonitorStateException

at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)

at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)

at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)

at LockTest.main(LockTest.java:12)

1

2

3

4

5

1

2

3

4

5

对于Semaphore来讲,如下:

public class SemaphoreTest {

publicstaticvoidmain(String[] args) {

Semaphore semaphore=new Semaphore(1);//总共有1个许可

System.out.println("可用的许可数目为:"+semaphore.availablePermits());

semaphore.release();

System.out.println("可用的许可数目为:"+semaphore.availablePermits());

}

}

1

2

3

4

5

6

7

8

1

2

3

4

5

6

7

8

结果如下:

可用的许可数目为:1

可用的许可数目为:2

1

2

1

2

i. 并没有抛出异常,也就是线程在调用release()之前并不要求先调用acquire()

ii. 我们看到可用的许可数目增加了一个,但我们的初衷是保证只有一个许可来达到互斥排他锁的目的,所以这里要注意一下

2 Semaphore(1)可以做到一个deadlock recovery,我们来看下面一个例子

class WorkThread2 extends Thread{

private Semaphore semaphore1,semaphore2;

publicWorkThread2(Semaphore semaphore1,Semaphore semaphore2){

this.semaphore1=semaphore1;

this.semaphore2=semaphore2;

}

publicvoidreleaseSemaphore2(){

System.out.println(Thread.currentThread().getId()+" 释放Semaphore2");

semaphore2.release();

}

publicvoidrun() {

try {

semaphore1.acquire(); //先获取Semaphore1

System.out.println(Thread.currentThread().getId()+" 获得Semaphore1");

TimeUnit.SECONDS.sleep(5); //等待5秒让WorkThread1先获得Semaphore2

semaphore2.acquire();//获取Semaphore2

System.out.println(Thread.currentThread().getId()+" 获得Semaphore2");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

class WorkThread1 extends Thread{

private Semaphore semaphore1,semaphore2;

publicWorkThread1(Semaphore semaphore1,Semaphore semaphore2){

this.semaphore1=semaphore1;

this.semaphore2=semaphore2;

}

publicvoidrun() {

try {

semaphore2.acquire();//先获取Semaphore2

System.out.println(Thread.currentThread().getId()+" 获得Semaphore2");

TimeUnit.SECONDS.sleep(5);//等待5秒,让WorkThread1先获得Semaphore1

semaphore1.acquire();//获取Semaphore1

System.out.println(Thread.currentThread().getId()+" 获得Semaphore1");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public class SemphoreTest {

publicstaticvoidmain(String[] args) throws InterruptedException {

Semaphore semaphore1=new Semaphore(1);

Semaphore semaphore2=new Semaphore(1);

new WorkThread1(semaphore1, semaphore2).start();

new WorkThread2(semaphore1, semaphore2).start();

//此时已经陷入了死锁,WorkThread1持有semaphore1的许可,请求semaphore2的许可

// WorkThread2持有semaphore2的许可,请求semaphore1的许可

// TimeUnit.SECONDS.sleep(10);

// //在主线程是否semaphore1,semaphore2,解决死锁

// semaphore1.release();

// semaphore2.release();

}

}

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

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

在注释最后面几行代码的情况下,结果为,陷入了一个死锁:

9 获得Semaphore2

10 获得Semaphore1

1

2

1

2

把注释删除,即在主线程释放Semaphore,这样就能解决死锁:

9 获得Semaphore2

10 获得Semaphore1

9 获得Semaphore1

10 获得Semaphore2

1

2

3

4

1

2

3

4

这即符合文档中说的,通过一个非owner的线程来实现死锁恢复,但如果你使用的是Lock则做不到,可以把代码中的两个信号量换成两个锁对象试试。很明显,前面也验证过了,要使用Lock.unlock()来释放锁,首先你得拥有这个锁对象,因此非owner线程(事先没有拥有锁)是无法去释放别的线程的锁对象

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值