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