这篇文章将介绍如何使用Guava中的Striped类来实现更细粒度的并发。 ConcurrentHashMap使用条带化锁定方法来增加并发性,并且Striped类通过赋予我们具有条带化Locks , ReadWriteLocks和Semaphores的能力来扩展此主体。 当访问对象或数据结构(例如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 hereThread.sleep(25);}finally{stripedSemaphore.release();}}public void nonStripedConcurrentAccess(String url) throws Exception{semaphore.acquire();try{//Access restricted resource hereThread.sleep(25);}finally{semaphore.release();}}
}
在此示例中,我们有两种方法ConcurrentWorker.stripedConcurrentAccess
和ConcurrentWorker.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>() {@Overridepublic 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
类并不适合所有情况,但当您需要并发不同数据时,它确实很方便。 谢谢你的时间。
资源资源
- 番石榴API
- 实践中的并发
- 这篇文章的源代码
翻译自: https://www.javacodegeeks.com/2013/09/fine-grained-concurrency-with-the-guava-striped-class.html