Java中的信号量Semaphore

参考资料:
1. http://blog.csdn.net/zmx729618/article/details/51593666
2. jdk官方文档


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();
        }
    }
}

结果如下:

9 正在吃饭
15 正在吃饭
13 正在吃饭
13 已经吃完
11 正在吃饭
10 正在吃饭
10 已经吃完
12 正在吃饭
14 正在吃饭
11 已经吃完
15 已经吃完
14 已经吃完
9 已经吃完
12 已经吃完

当我们在构造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();
    }
}

则会抛出异常,因为该线程事先并没有获取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)

对于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

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();
    }

}

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

9 获得Semaphore2
10 获得Semaphore1

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

9 获得Semaphore2
10 获得Semaphore1
9 获得Semaphore1
10 获得Semaphore2

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

你可能感兴趣的:(Java)