Semaphore安全释放

Semaphore应该比较熟悉了,从学习操作系统时就听过了,可以控制并行线程的数量。常用的使用方法如下

public class Main {
    static Semaphore semaphore = new Semaphore(1);

    public static void doBusinessMethod(String str) {
        try {
            semaphore.acquire(1);
            businessMethod(str);
            semaphore.release(1);
        } catch (InterruptedException e) {
            System.out.println("get semaphore interrupted.");
        } catch (Exception e) {
            System.out.println("do business exception.");
        }
    }

    public static void businessMethod(String str) {
        Objects.requireNonNull(str);
        System.out.printf("do businessMethod %s.", str);
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->doBusinessMethod(null)).start();
        Thread.sleep(1000);
        new Thread(()->doBusinessMethod("Thread2")).start();
    }

}

在try block中释放release无法保证能够正确释放资源,在源码注释中可以看到acquire方法能自动释放获得的锁,但是这里是容易让人误解的

If the current thread:

  • has its interrupted status set on entry to this method;or
  • is interrupted while waiting for a permit,

hen InterruptedException is thrown and the current thread's interrupted status is cleared. Any permits that were to be assigned to this thread are instead assigned to other threads trying to acquire permits, as if permits had been made available by a call to release().

 这段注释中并没有说到这里如果在获得acquire获得锁之后,如果产生异常会自动释放锁,因此上面的写法在不能正确处理异常时会产出死锁。因此,我们就会使用如下一种写法:

import java.util.Objects;
import java.util.concurrent.Semaphore;

public class Main {
    static Semaphore semaphore = new Semaphore(1);

    public static void doBusinessMethod(String str) {
        try {
            semaphore.acquire(1);
            businessMethod(str);
        } catch (InterruptedException e) {
            System.out.println("get semaphore interrupted.");
        } catch (Exception e) {
            System.out.println("do business exception.");
        } finally {
            semaphore.release(1);
        }
    }

}

我们将释放release操作放在finally中,这就能保证释放操作一定能执行。但是却产生了一个新的问题,因为释放操作并不判断线程是否获得锁,所以如果acquire没有获得资源(例如超时了),那么就会导致semaphore计数增加。使用tryAcquire时也会有相同的问题。

综上,Semaphore的确存在缺陷,至少在使用上有些坑,上面的例子主要是为了描述,semaphore安全释放很重要。实现semaphore安全释放有以下两种方法

  • 通过标志量判断是否获得信号量,只有在获得信号量的前提下释放锁。
import java.util.Objects;
import java.util.concurrent.Semaphore;

public class Main {
    static Semaphore semaphore = new Semaphore(1);

    public static void doBusinessMethod(String str) {
        boolean acquired = false;
        try {
            semaphore.acquire(1);
            acquired = true;
            businessMethod(str);
        } catch (InterruptedException e) {
            System.out.println("get semaphore interrupted.");
        } catch (Exception e) {
            System.out.println("do business exception.");
        } finally {
            if (acquired) {
                semaphore.release(1);
            }
            
        }
    }

}
  • 通过set判断线程是否持有锁,如果线程持有锁才能执行释放操作release
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    
    public class SafeSemaphore extends Semaphore {
    
        private static final Object object = new Object();
        private final ConcurrentHashMap<Thread, Object> threadSet;
    
        public SafeSemaphore(int permits) {
            super(permits);
            threadSet = new ConcurrentHashMap<>(permits);
        }
    
        public SafeSemaphore(int permits, boolean fair) {
            super(permits, fair);
            threadSet = new ConcurrentHashMap<>(permits);
        }
    
        @Override
        public void acquire() throws InterruptedException {
            super.acquire();
            threadSet.put(Thread.currentThread(), object);
        }
    
        @Override
        public void release() {
            final Thread thread = Thread.currentThread();
            if (threadSet.containsKey(thread)) {
                super.release();
                threadSet.remove(thread);
            }
        }
    
        @Override
        public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {
            if (super.tryAcquire(timeout, unit)) {
                threadSet.put(Thread.currentThread(), object);
                return true;
            }
            return false;
        }
    }

     

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值