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线程(事先没有拥有锁)是无法去释放别的线程的锁对象。
==========================================================================
如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】
希望我们一起在架构的路上,像鹿一样追逐,也想鹿一样优雅
==========================================================================