zoukankan      html  css  js  c++  java
  • 并发编程中: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线程(事先没有拥有锁)是无法去释放别的线程的锁对象。

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

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

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

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

    ==========================================================================           如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】,希望我们一起在架构的路上,并肩齐行
    ==========================================================================
  • 相关阅读:
    Redis常见7种使用场景(PHP)
    阻塞式I/O实现简单TCP通信
    telnet客户端程序
    TCP简单回射程序
    getsockname和getpeername函数
    close函数
    TCP时间获取程序
    listen函数
    基本套接字编程
    readline.c
  • 原文地址:https://www.cnblogs.com/amberJava/p/12355163.html
Copyright © 2011-2022 走看看