番石榴条纹类的细粒度并发

这篇文章将介绍如何使用Guava中的Striped类来实现更细粒度的并发。 ConcurrentHashMap使用条带化锁定方法来增加并发性,并且Striped类通过赋予我们具有条带化LocksReadWriteLocksSemaphores的能力来扩展此主体。 当访问对象或数据结构(例如Array或HashMap)时,通常我们将在整个对象或数据结构上进行同步,但这是否总是必要的? 在某些情况下,答案是肯定的,但有时我们可能没有达到这种程度的进程锁定,并且可以通过使用更精细的方法来提高应用程序的性能。

条纹锁定/信号量的用例

始终同步可能的最小部分代码被视为最佳实践。 换句话说,我们只想在更改共享数据的代码部分之间进行同步。 通过允许我们减少不同任务的同步,条带化使这一步骤更进一步。 例如,假设我们有一个表示远程资源的URL的ArrayList,并且我们想限制在任何给定时间访问这些资源的线程总数。 限制对N个线程的访问自然适合java.util.concurrent.Semaphore对象。 但是问题在于限制对我们ArrayList的访问成为瓶颈。 这是Striped类提供帮助的地方。 我们真正想要做的是限制对资源的访问,而不是对URL的容器的访问。 假设每一个都是不同的,我们使用一种“条带化”的方法,其中一次访问每个 URL仅限于N个线程,从而提高了应用程序的吞吐量和响应能力。

创建条纹锁/信号灯

要创建Striped实例,我们只需使用Striped类中可用的静态因子方法之一即可。 例如,这是我们如何创建带区卷ReadWriteLock的实例:

Striped<ReadWriteLock> rwLockStripes = Striped.readWriteLock(10);

在上面的示例中,我们创建了一个Striped实例,在该实例中急切创建了单独的“条纹”,强引用。 影响是除非删除Striped对象本身,否则不会垃圾收集锁/信号灯。 但是,当创建锁/信号量时,Stranded类为我们提供了另一个选择:

int permits = 5;
int numberStripes = 10;
Striped<Semaphore> lazyWeakSemaphore = Striped.lazyWeakSemaphore(numberStripes,permits);

在此示例中, Semaphore实例将被延迟创建并包装在WeakReferences因此可立即用于垃圾回收,除非有另一个对象挂在其上。

访问/使用条纹锁/信号灯

要使用之前创建的Striped ReadWriteLock ,请执行以下操作:

String key = "taskA";
ReadWriteLock rwLock = rwLockStripes.get(key);
try{
     rwLock.lock();
     .....
}finally{
     rwLock.unLock();
}

通过调用Striped.get方法,我们将返回一个与给定对象键相对应的ReadWriteLock实例。 我们将在下面使用Striped类提供更多示例。

锁/信号量检索保证

当检索锁/信号量实例时,Striped类保证objectA等于objectB(假设正确实现了equals和hashcode方法),对Striped.get方法的调用将返回相同的锁/信号量实例。 但是不能保证相反的情况,也就是说,当objectA不等于ObjectB时,我们仍然可以检索相同引用的锁/信号量。 根据针对Striped类的Javadoc,指定更少的条纹会增加为两个不同的键获得相同引用的可能性。

一个例子

让我们基于引言中介绍的场景来看一个非常简单的示例。 我们构造了一个ConcurrentWorker类,在其中我们希望限制对某些资源的并发访问。 但让我们假设有一些不同的资源,因此理想情况下,我们希望限制每个资源的访问,而不仅仅是限制ConcurrentWorker

public class ConcurrentWorker {

    private Striped<Semaphore> stripedSemaphores = Striped.semaphore(10,3);
    private Semaphore semaphore = new Semaphore(3);

    public void stripedConcurrentAccess(String url) throws Exception{
       Semaphore stripedSemaphore  = stripedSemaphores.get(url);
        stripedSemaphore.acquire();
       try{
           //Access restricted resource here
           Thread.sleep(25);
       }finally{
           stripedSemaphore.release();
       }
    }

    public void nonStripedConcurrentAccess(String url) throws Exception{
        semaphore.acquire();
        try{
           //Access restricted resource here
            Thread.sleep(25);
        }finally{
            semaphore.release();
        }
    }
}

在此示例中,我们有两种方法ConcurrentWorker.stripedConcurrentAccessConcurrentWorker.nonStripedConcurrentAccess因此我们可以轻松进行比较。 在这两种情况下,我们都通过调用Thread.sleep 25毫秒来模拟工作。 我们创建了一个Semaphore实例和一个Striped实例,其中包含10个急切创建的,高度引用的Semaphore对象。 在这两种情况下,我们都指定了3个许可,因此任何时候3个线程都可以访问我们的“资源”。 这是一个简单的驱动程序类,用于测量两种方法之间的吞吐量。

public class StripedExampleDriver {

    private ExecutorService executorService = Executors.newCachedThreadPool();
    private int numberThreads = 300;
    private CountDownLatch startSignal = new CountDownLatch(1);
    private CountDownLatch endSignal = new CountDownLatch(numberThreads);
    private Stopwatch stopwatch = Stopwatch.createUnstarted();
    private ConcurrentWorker worker = new ConcurrentWorker();
    private static final boolean USE_STRIPES = true;
    private static final boolean NO_STRIPES = false;
    private static final int POSSIBLE_TASKS_PER_THREAD = 10;
    private List<String> data = Lists.newArrayList();

    public static void main(String[] args) throws Exception {
        StripedExampleDriver driver = new StripedExampleDriver();
        driver.createData();
        driver.runStripedExample();
        driver.reset();
        driver.runNonStripedExample();
        driver.shutdown();
    }

    private void runStripedExample() throws InterruptedException {
        runExample(worker, USE_STRIPES, "Striped work");
    }

    private void runNonStripedExample() throws InterruptedException {
        runExample(worker, NO_STRIPES, "Non-Striped work");
    }

    private void runExample(final ConcurrentWorker worker, final boolean isStriped, String type) throws InterruptedException {
        for (int i = 0; i < numberThreads; i++) {
            final String value = getValue(i % POSSIBLE_TASKS_PER_THREAD);
            executorService.submit(new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    startSignal.await();
                    if (isStriped) {
                        worker.stripedConcurrentAccess(value);
                    } else {
                        worker.nonStripedConcurrentAccess(value);
                    }
                    endSignal.countDown();
                    return null;
                }
            });
        }
        stopwatch.start();
        startSignal.countDown();
        endSignal.await();
        stopwatch.stop();
        System.out.println("Time for" + type + " work [" + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "] millis");
    }
  //details left out for clarity

我们使用了300个线程,并分别两次调用了这两个方法,并使用StopWach类记录了时间。

结果

如您所料,条带化版本的性能要好得多。 这是其中一个测试运行的控制台输出:

Time forStriped work work [261] millis
Time forNon-Striped work work [2596] millis

虽然Striped类并不适合所有情况,但当您需要并发不同数据时,它确实很方便。 谢谢你的时间。

资源资源

参考:随机编码》博客上的JCG合作伙伴 Bill Bejeck 提供的与Guava Striped Class的细粒度并发。

翻译自: https://www.javacodegeeks.com/2013/09/fine-grained-concurrency-with-the-guava-striped-class.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值