Java并发编程入门(二十六)无锁方式获取池化资源

443 阅读4分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是铿然架构的第 91 篇原创文章

相关阅读:

Java并发编程(一)知识地图
Java并发编程(二)原子性
Java并发编程(三)可见性
Java并发编程(四)有序性
Java并发编程(五)创建线程方式概览
Java并发编程入门(六)synchronized用法
Java并发编程入门(七)轻松理解wait和notify以及使用场景
Java并发编程入门(八)线程生命周期
Java并发编程入门(九)死锁和死锁定位
Java并发编程入门(十)锁优化
Java并发编程入门(十一)限流场景和Spring限流器实现
Java并发编程入门(十二)生产者和消费者模式-代码模板
Java并发编程入门(十三)读写锁和缓存模板
Java并发编程入门(十四)CountDownLatch应用场景
Java并发编程入门(十五)CyclicBarrier应用场景
Java并发编程入门(十六)秒懂线程池差别
Java并发编程入门(十七)一图掌握线程常用类和接口
Java并发编程入门(十九)异步任务调度工具CompleteFeature
Java并发编程入门(二十)常见加锁场景和加锁工具
Java并发编程入门(二十一)volatile关键字
Java并发编程入门(二十二)ThreadLocal变量
Java并发编程入门(二十三)守护线程
Java并发编程入门(二十四)Java原子类
Java并发编程入门(二十五)DelayQueue


1. 无锁获取池化资源

无锁获取资源的原理为:

wusuo_01.jpg

wusuo_02.jpg

1.通过一个共享资源池存放资源

2.资源有状态,分为未使用和已使用

3.获取资源时通过遍历资源池中的资源,将资源状态从未使用更新为已使用,如果更新成功,则返回资源给使用者。

2. 代码示例

2.1. 类结构

wusuo_03.jpg

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.使用无锁机制的目的通常是避免加锁引起性能降低,但是无锁究竟能提供高多少性能,在哪些场景下适用,还需根据业务场景验证,有的场景可能并没有必要,基本是出于开发者的喜好决定使用什么方式。


<--阅过留痕,左边点赞!