并发编程中:Semaphore信号量与lock的区别

Semaphore,信号量,常用于限制可以访问某些资源的线程数量,比如连接池、对象池、线程池等等。其中,你可能最熟悉数据库连接池,在同一时刻,一定是允许多个线程同时使用连接池的,当然,每个连接在被释放前,是不允许其他线程使用的。

信号量实现了一个最简单的互斥锁功能。估计你会觉得奇怪,既然有 Java SDK 里面提供了 Lock,为啥还要提供一个 Semaphore ?其实实现一个互斥锁,仅仅是 Semaphore 的部分功能,Semaphore 还有一个功能是 Lock 不容易实现的,那就是: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.

 

 

1.LocK情况

public class LockTest {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();
    //Lock.lock() 注释掉lock,就会报错,因为没有获得锁,就进行释放 lock.unlock(); //Lock.unlock()之前,该线程必须事先持有这个锁,否则抛出异常 }

 

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) 

 

2.Semaphore情况

public class Semaphore {    
    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

结果说明了以下2个问题:

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

2.我们看到可用的许可数目增加了一个,但我们的初衷是保证只有一个许可来达到互斥排他锁的目的.

 

这就是 Semaphore的另一个用途: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,解决死锁
// System.Out.PrintLn("===释放信号=="); // semaphore1.release(); // semaphore2.release(); } }

 

输出结果:

获得Semaphore2
获得Semaphore1

当我们打开最后的release代码块:

获得Semaphore2
获得Semaphore1
===释放信号==
获得Semaphore1
获得Semaphore2

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

 

==========================================================================

  如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞

  希望我们一起在架构的路上,像鹿一样追逐,也想鹿一样优雅

==========================================================================

 

 

 

上一篇:Java多线程并发工具类-信号量Semaphore对象讲解


下一篇:java之Semaphore使用介绍