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个人同时吃饭,则如下:

 
  1.  
    class EatThread extends Thread{
  2.  
    private Semaphore semaphore;
  3.  
    public EatThread(Semaphore semaphore){
  4.  
    this.semaphore=semaphore;
  5.  
    }
  6.  
  7.  
    public void run(){
  8.  
    try {
  9.  
    semaphore.acquire();//获取一个许可,当然也可以调用acquire(int),这样一个线程就能拿到多个许可
  10.  
    long eatTime=(long) (Math.random()*10);
  11.  
    System.out.println(Thread.currentThread().getId()+" 正在吃饭");
  12.  
    TimeUnit.SECONDS.sleep(eatTime);
  13.  
    System.out.println(Thread.currentThread().getId()+" 已经吃完");
  14.  
    semaphore.release();//归还许可
  15.  
    } catch (InterruptedException e) {
  16.  
    e.printStackTrace();
  17.  
    }
  18.  
    }
  19.  
    }
  20.  
    public class SemaphoreTest {
  21.  
    public static void main(String[] args) {
  22.  
    Semaphore semaphore=new Semaphore(5);//总共有5个许可
  23.  
    for(int i=0;i<7;i++){//定义七个吃的线程
  24.  
    new EatThread(semaphore).start();
  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
  • 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.  
    9 正在吃饭
  2.  
    15 正在吃饭
  3.  
    13 正在吃饭
  4.  
    13 已经吃完
  5.  
    11 正在吃饭
  6.  
    10 正在吃饭
  7.  
    10 已经吃完
  8.  
    12 正在吃饭
  9.  
    14 正在吃饭
  10.  
    11 已经吃完
  11.  
    15 已经吃完
  12.  
    14 已经吃完
  13.  
    9 已经吃完
  14.  
    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()获取),如下:

 
  1.  
    public class LockTest {
  2.  
    public static void main(String[] args) {
  3.  
    Lock lock=new ReentrantLock();
  4.  
    lock.unlock();
  5.  
    }
  6.  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

 
  1.  
    Exception in thread "main" java.lang.IllegalMonitorStateException
  2.  
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
  3.  
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
  4.  
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
  5.  
    at LockTest.main(LockTest.java:12)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

对于Semaphore来讲,如下:

 
  1.  
    public class SemaphoreTest {
  2.  
    public static void main(String[] args) {
  3.  
    Semaphore semaphore=new Semaphore(1);//总共有1个许可
  4.  
    System.out.println("可用的许可数目为:"+semaphore.availablePermits());
  5.  
    semaphore.release();
  6.  
    System.out.println("可用的许可数目为:"+semaphore.availablePermits());
  7.  
    }
  8.  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

结果如下:

 
  1.  
    可用的许可数目为:1
  2.  
    可用的许可数目为:2
  • 1
  • 2
  • 1
  • 2

i. 并没有抛出异常,也就是线程在调用release()之前并不要求先调用acquire() 
ii. 我们看到可用的许可数目增加了一个,但我们的初衷是保证只有一个许可来达到互斥排他锁的目的,所以这里要注意一下

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

 
  1.  
    class WorkThread2 extends Thread{
  2.  
    private Semaphore semaphore1,semaphore2;
  3.  
    public WorkThread2(Semaphore semaphore1,Semaphore semaphore2){
  4.  
    this.semaphore1=semaphore1;
  5.  
    this.semaphore2=semaphore2;
  6.  
    }
  7.  
    public void releaseSemaphore2(){
  8.  
    System.out.println(Thread.currentThread().getId()+" 释放Semaphore2");
  9.  
    semaphore2.release();
  10.  
    }
  11.  
    public void run() {
  12.  
    try {
  13.  
    semaphore1.acquire(); //先获取Semaphore1
  14.  
    System.out.println(Thread.currentThread().getId()+" 获得Semaphore1");
  15.  
    TimeUnit.SECONDS.sleep(5); //等待5秒让WorkThread1先获得Semaphore2
  16.  
    semaphore2.acquire();//获取Semaphore2
  17.  
    System.out.println(Thread.currentThread().getId()+" 获得Semaphore2");
  18.  
    } catch (InterruptedException e) {
  19.  
    e.printStackTrace();
  20.  
    }
  21.  
    }
  22.  
    }
  23.  
    class WorkThread1 extends Thread{
  24.  
    private Semaphore semaphore1,semaphore2;
  25.  
    public WorkThread1(Semaphore semaphore1,Semaphore semaphore2){
  26.  
    this.semaphore1=semaphore1;
  27.  
    this.semaphore2=semaphore2;
  28.  
    }
  29.  
    public void run() {
  30.  
    try {
  31.  
    semaphore2.acquire();//先获取Semaphore2
  32.  
    System.out.println(Thread.currentThread().getId()+" 获得Semaphore2");
  33.  
    TimeUnit.SECONDS.sleep(5);//等待5秒,让WorkThread1先获得Semaphore1
  34.  
    semaphore1.acquire();//获取Semaphore1
  35.  
    System.out.println(Thread.currentThread().getId()+" 获得Semaphore1");
  36.  
    } catch (InterruptedException e) {
  37.  
    e.printStackTrace();
  38.  
    }
  39.  
    }
  40.  
    }
  41.  
    public class SemphoreTest {
  42.  
    public static void main(String[] args) throws InterruptedException {
  43.  
    Semaphore semaphore1=new Semaphore(1);
  44.  
    Semaphore semaphore2=new Semaphore(1);
  45.  
    new WorkThread1(semaphore1, semaphore2).start();
  46.  
    new WorkThread2(semaphore1, semaphore2).start();
  47.  
    //此时已经陷入了死锁,WorkThread1持有semaphore1的许可,请求semaphore2的许可
  48.  
    // WorkThread2持有semaphore2的许可,请求semaphore1的许可
  49.  
    // TimeUnit.SECONDS.sleep(10);
  50.  
    // //在主线程是否semaphore1,semaphore2,解决死锁
  51.  
    // semaphore1.release();
  52.  
    // semaphore2.release();
  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
  • 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.  
    9 获得Semaphore2
  2.  
    10 获得Semaphore1
  • 1
  • 2
  • 1
  • 2

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

 
  1.  
    9 获得Semaphore2
  2.  
    10 获得Semaphore1
  3.  
    9 获得Semaphore1
  4.  
    10 获得Semaphore2
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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

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