Java中的Semaphore和Lock区别

来源:互联网 发布:ie js 表格导出excel 编辑:程序博客网 时间:2024/05/16 09:23

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

class EatThread extends Thread{    private Semaphore semaphore;    public EatThread(Semaphore semaphore){        this.semaphore=semaphore;    }    @Override    public void run(){        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 class SemaphoreTest {        public static void main(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 {    public static void main(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 {        public static void main(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;    public WorkThread2(Semaphore semaphore1,Semaphore semaphore2){        this.semaphore1=semaphore1;        this.semaphore2=semaphore2;    }    public void releaseSemaphore2(){        System.out.println(Thread.currentThread().getId()+" 释放Semaphore2");        semaphore2.release();    }    public void run() {        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;        public WorkThread1(Semaphore semaphore1,Semaphore semaphore2){            this.semaphore1=semaphore1;            this.semaphore2=semaphore2;        }        public void run() {            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 {    public static void main(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 获得Semaphore210 获得Semaphore1
  • 1
  • 2
  • 1
  • 2

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

9 获得Semaphore210 获得Semaphore19 获得Semaphore110 获得Semaphore2
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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

0 0
原创粉丝点击