使用每个密钥锁实现线程安全服务

时间:2018-05-30 11:26:24

标签: java multithreading locking reentrantlock

我的目标是实现一个对Widgets进行操作的服务,该服务具有属性name。该服务的操作由多个较小的步骤组成,这些步骤应按name同步执行,这意味着不能对同名的小部件进行两次操作交织。

最简单的解决方案是完全锁定这些操作,例如使用这样的EJB:

@Singleton
public class WidgetService {
    @Lock(LockType.WRITE)
    public void doThing(Widget widget) {
        // many individual steps
    }
}

但是,这也意味着对不同名称的小部件的操作不能并行执行,这会严重影响我的性能。所以我的解决方案是实现一个名字锁定机制。 Lock本身看起来大致如下:

public class NameLock implements AutoCloseable {

    private static final ConcurrentMap<String, ReentrantReadWriteLock> globalLocks = new ConcurrentHashMap<>();

    private final String name;

    private NameLock(String name) {
        this.name = name;
        final ReentrantReadWriteLock lock = globalLocks.computeIfAbsent(name, s -> new ReentrantReadWriteLock());
        lock.writeLock().lock();
    }

    @Override
    public void close() {
        final ReentrantReadWriteLock lock = globalLocks.get(name);
        lock.writeLock().unlock();
    }
}

并使用如下:

public class WidgetService {
    public void doThing(Widget widget) {
        try (NameLock lock = new NameLock(widget.getName())) {
            // many individual steps
        }
    }
}

但是,这意味着globalLocks地图会填充永远不会被清理的锁。我不能简单地在close方法中调用globalLocks.remove(name),因为由于并行性,另一个线程可能已经检索到该锁并且在lock()方法被阻止。有一种优雅的方式,我可以通过线程安全,非内存泄漏的方式实现这一目标吗?

我为此编写了一个测试,如果锁定按预期工作,它应该快速完成而不会出错。这传递给我的实现,但仍然在地图中留下锁:

@Test
public void lockMultiple() throws Exception {
    final int nThreads = 10;
    final int nTasks = 200;
    final Random random = new Random();

    final ExecutorService threads = Executors.newFixedThreadPool(nThreads);
    final Function<Integer, Integer> func = i -> {
        try (NameLock lock = new NameLock("foo")) {
            // fuzzing
            Thread.sleep(random.nextInt(10));
        }
        return i;
    };
    final List<Future<Integer>> futures = IntStream.range(0, nTasks)
            .mapToObj(i -> (Callable<Integer>) () -> func.apply(i))
            .map(threads::submit)
            .collect(Collectors.toList());
    for (Future<Integer> future : futures) {
        final Integer i = future.get(10, TimeUnit.SECONDS);
    }
}

0 个答案:

没有答案