29. 无锁方式获取池化资源
大约 3 分钟
1. 无锁获取池化资源
无锁获取资源的原理为:
1.通过一个共享资源池存放资源
2.资源有状态,分为未使用和已使用
3.获取资源时通过遍历资源池中的资源,将资源状态从未使用更新为已使用,如果更新成功,则返回资源给使用者。
2. 代码示例
2.1. 类结构
2.2. 代码
2.2.1. Resource.java
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class Resource implements Closeable {
public static final int STATE_IN_USE = 1;
public static final int STATE_NOT_IN_USE = 2;
private static AtomicIntegerFieldUpdater<Resource> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(Resource.class, "state");
private volatile int state;
private String name;
Resource(String name) {
this.name = name;
}
// 包级访问,仅需对ResourceCreator可见
void setState(int update) {
stateUpdater.set(this, update);
}
// 包级访问,仅需对ResourcePool可见
int getState() {
return state;
}
// 包级访问,仅需对ResourcePool可见
boolean compareAndSet(int expect, int update) {
return stateUpdater.compareAndSet(this, expect, update);
}
@Override
public void close() throws IOException {
stateUpdater.set(this, STATE_NOT_IN_USE);
System.out.println(this + " be released.");
}
@Override
public String toString() {
return name;
}
}
2.2.2. Resource.java
public class ResourceCreator {
// 模拟资源创建,包级访问,仅对ResourcePool可见
Resource create(String name) {
Resource resource = new Resource(name);
resource.setState(Resource.STATE_NOT_IN_USE);
return resource;
}
}
2.2.3. ResourcePool.java
import java.util.concurrent.CopyOnWriteArrayList;
public class ResourcePool {
public static final int POOL_SIZE = 5;
private static volatile ResourcePool resourcePool;
private static CopyOnWriteArrayList<Resource> sharedList;
private ResourcePool() {
sharedList = new CopyOnWriteArrayList<>();
initPool();
}
public static ResourcePool getInstance() {
if (resourcePool == null) {
synchronized (ResourcePool.class) {
if (resourcePool == null) {
resourcePool = new ResourcePool();
}
}
}
return resourcePool;
}
public Resource getResource() throws Exception {
for (Resource resource: sharedList) {
if (resource.compareAndSet(Resource.STATE_NOT_IN_USE, Resource.STATE_IN_USE)) {
return resource;
}
}
throw new Exception("no resources available.");
}
public int getIdleRourceNum() {
int count = 0;
for (Resource resource: sharedList) {
if (resource.getState() == Resource.STATE_NOT_IN_USE) {
count = count + 1;
}
}
return count;
}
private static void initPool() {
ResourceCreator creator = new ResourceCreator();
for (int i = 0; i < POOL_SIZE; i++) {
String resourcName = "resource_" + (i+1);
Resource resource = creator.create(resourcName);
sharedList.add(resource);
}
}
}
2.2.4. ResourceDemo.java
import java.util.concurrent.TimeUnit;
public class ResourceDemo {
// 模拟资源使用者
private static class ResourceClient implements Runnable {
@Override
public void run() {
try(Resource resource = ResourcePool.getInstance().getResource()) { // 会自动调用close方法释放资源
System.out.println("get resource: " + resource);
quietlySleep(); // 模拟执行耗费时间
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void quietlySleep() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
}
public static void main(String[] args) {
final int threadNum = ResourcePool.POOL_SIZE + 3;
for (int i = 0; i < threadNum; i++) {
new Thread(new ResourceClient()).start();
System.out.println("thread no: " + i);
}
quietlySleep(); // 休眠,检查资源是否会释放
System.out.println("IdleRourceNum: " + ResourcePool.getInstance().getIdleRourceNum());
}
}
打印日志:
thread no: 0
thread no: 1
thread no: 2
thread no: 3
thread no: 4
thread no: 5
thread no: 6
thread no: 7
get resource: resource_1
get resource: resource_4
get resource: resource_3
get resource: resource_2
get resource: resource_5
java.lang.Exception: no resources available.
at com.javashizhan.concurrent.demo.pool.ResourcePool.getResource(ResourcePool.java:32)
at com.javashizhan.concurrent.demo.pool.ResourceDemo$ResourceClient.run(ResourceDemo.java:11)
at java.lang.Thread.run(Thread.java:745)
java.lang.Exception: no resources available.
at com.javashizhan.concurrent.demo.pool.ResourcePool.getResource(ResourcePool.java:32)
at com.javashizhan.concurrent.demo.pool.ResourceDemo$ResourceClient.run(ResourceDemo.java:11)
at java.lang.Thread.run(Thread.java:745)
java.lang.Exception: no resources available.
at com.javashizhan.concurrent.demo.pool.ResourcePool.getResource(ResourcePool.java:32)
at com.javashizhan.concurrent.demo.pool.ResourceDemo$ResourceClient.run(ResourceDemo.java:11)
at java.lang.Thread.run(Thread.java:745)
IdleRourceNum: 5
resource_2 be released.
resource_4 be released.
resource_1 be released.
resource_3 be released.
resource_5 be released.
从日志可以看到: 1.有8个线程并发获取资源
2.每个线程执行时间较长,因此8个并发线程中只有5个能获取到资源,另外3个无法获取到资源。
3.主线程休眠一段时间后,资源被释放,可用资源数量恢复到使用前数量。
3. 总结
1.通过无锁机制也能做到线程安全,其核心是确保CAS(compareAndSet)操作是原子操作。
2.使用无锁机制的目的通常是避免加锁引起性能降低,但是无锁究竟能提供高多少性能,在哪些场景下适用,还需根据业务场景验证,有的场景可能并没有必要,基本是出于开发者的喜好决定使用什么方式。