Java并发之Semaphore

Semaphore是juc包下一个并发辅助类,官方定义为可以计数的信号量,允许N个获得许可的线程同时访问某个资源或执行某段代码,许可可以被归还,归还后的许可可以被其他线程获取,如果许可不足,线程就等待,当其他线程归还许可,等待的线程被唤醒,尝试获取许可

注意:许可可以被同一个线程获取多次,也能够被同一个线程归还多次。

当资源有限时,并发获取使用Semaphore控制是一个非常好的方式,比如数据库连接池,网络连接池等。

Semaphore主要方法有:

  • acquire:阻塞获取许可,直到获得许可为止,响应interrupt
  • acquireUninterruptibly:阻塞获取许可,直到获得许可为止,不响应interrupt
  • tryAcquire:尝试获取许可,返回boolean类型,true表示获得,false未获得
  • release:释放(归还)许可,同时唤醒所有尝试获取许可的线程

当然还有一些其他的方法,不过语义上大同小异。

Semaphore内部通过继承AQS,预先申请许可,同时实现类FairNonfair的等待唤醒机制

同CountDownLatch类似,都是通过AQS来实现内部的流程,juc下面还有不少工具也是通过AQS来实现,之后会详细介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class Pool {
private final Stack<Connection> stack;
private final Semaphore semaphore;

public static void main(String[] args) {
Pool pool = new Pool(2);
Thread[] threads = new Thread[8];
for (int i = 0; i < 8; i++) {
Thread t = new Thread(new Runnable() {

@Override
public void run() {
Connection connection = pool.borrowConnection();
System.out.println(Thread.currentThread().getName() + " borrowed " + connection);
pool.returnConnection(connection);
System.out.println(Thread.currentThread().getName() + " return " + connection);
}
});
threads[i] = t;
}
for (int i = 0; i < 8; i++) {
threads[i].start();
}
}

public Pool(int size) {
// 初始化许可
stack = new Stack<Connection>();
for (int i = 0; i < size; i++) {
stack.push(new Connection(i));
}
semaphore = new Semaphore(size);
}

public Connection borrowConnection() {
// 尝试获取许可,如果没有就阻塞
semaphore.acquireUninterruptibly();
// 获取连接
return stack.pop();
}

public void returnConnection(Connection connection) {
// 返回连接
stack.push(connection);
// 归还许可,其他线程可以获得该许可
semaphore.release();
}

static class Connection {
private final int id;

public Connection(int id) {
this.id = id;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Connection [id=").append(id).append("]");
return builder.toString();
}

}

}

下面是输出,可以看到获取连接的线程是没有顺序的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Thread-0 borrowed Connection [id=0]
Thread-6 borrowed Connection [id=0]
Thread-6 return Connection [id=0]
Thread-0 return Connection [id=0]
Thread-7 borrowed Connection [id=0]
Thread-7 return Connection [id=0]
Thread-1 borrowed Connection [id=0]
Thread-1 return Connection [id=0]
Thread-2 borrowed Connection [id=0]
Thread-2 return Connection [id=0]
Thread-3 borrowed Connection [id=0]
Thread-3 return Connection [id=0]
Thread-4 borrowed Connection [id=0]
Thread-4 return Connection [id=0]
Thread-5 borrowed Connection [id=0]
Thread-5 return Connection [id=0]

如果我们把Pool的构造方法中的

1
semaphore = new Semaphore(size);

改成

1
semaphore = new Semaphore(size, true);

输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Thread-0 borrowed Connection [id=0]
Thread-0 return Connection [id=0]
Thread-1 borrowed Connection [id=0]
Thread-1 return Connection [id=0]
Thread-2 borrowed Connection [id=0]
Thread-2 return Connection [id=0]
Thread-3 borrowed Connection [id=0]
Thread-3 return Connection [id=0]
Thread-4 borrowed Connection [id=0]
Thread-4 return Connection [id=0]
Thread-5 borrowed Connection [id=0]
Thread-5 return Connection [id=0]
Thread-6 borrowed Connection [id=0]
Thread-6 return Connection [id=0]
Thread-7 borrowed Connection [id=0]
Thread-7 return Connection [id=0]

获取连接的顺序固定了,这就是公平和非公平的区别,公平策略会先到先得,内部是一个队列,而非公平的策略并非先到先得,内部是一个自旋的过程,通俗说就是有人插队^_^。

Semaphore可以提供许可,让有限的资源被多个线程共享使用。