我有一个字符串集合,必须由一个线程锁定以计算一些信息。这些字符串只是唯一的实体,一次只能由一个线程处理。除了当前线程尝试获取集合中任何这些实体的锁之外的任何线程必须等待当前线程完成其执行并释放对集合的锁定。包含不同字符串的两个完全不同的集合可以由两个不同的线程同时处理。我被困在如何实现这一点。我试过谷歌搜索。但我发现的只是如何锁定单个字符串而不是很多字符串。任何想法都会有所帮助。
例如,
线程一是正在研究苹果,橘子和香蕉
线程二想要在苹果和桃子上工作。
线程2必须等到线程1释放锁定。
更新
而不是看着锁从字面上认为它是使用该实体的许可。例如,如果您需要使用Apple,则必须获得许可。线程必须阻塞,直到它获得其集合中所有实体的权限。
让我们以这种方式表达。 Thread1 [apples,oranges,bananas]寻求允许将集合中的实体用于Manager。让我们假设经理授予Thread1许可。如果另一个线程Thread2 [apples,peaches]寻求权限,则管理器不应该授予权限,因为{1}}的权限已经由Thread1拥有,并且必须阻止Thread2。一旦Thread1告诉管理器它已完成其工作,Manager可以授予Thread2权限。同时,如果另一个线程Thread3 [番石榴,菠萝]寻求许可,管理者不应该阻止它并随时授予许可。
答案 0 :(得分:0)
我会选择一个"锁定"的同步存储,而不是在许多锁定上运行。字符串。 这将大大简化同步 这样的事情:
private final Set<String> elementsInProgress = new HashSet<String>(); // not thread-safe, accessed only from blocks synchronized on it
public void process(Collection<String> input) throws InterruptedException {
for (String stringToProcess : input) {
synchronized (elementsInProgress) {
while (elementsInProgress.contains(stringToProcess)) {
elementsInProgress.wait();
}
elementsInProgress.add(stringToProcess);
}
doProcess(stringToProcess);
synchronized (elementsInProgress) {
elementsInProgress.remove(stringToProcess);
elementsInProgress.notifyAll();
}
}
}
private void doProcess(String s){/* ... */}
代码段未经测试,顺便说一句
答案 1 :(得分:0)
以guava-libraries
import com.google.common.collect.Interners;
import com.google.common.collect.Interner;
然后创建一个interner。弱引用是好的b / c它是持有锁的特定实例。您可以使用ConcurrentMap(小心使用putIfAbsent)进行实习,但是......
Interner<String> namedLocks = Interners.newWeakInterner();
然后客户端线程可以简单地使用synchronized。我正在使用Callable
来代表作品。
public <T> void doWork(String name, Callable<T> work) {
synchronized(namedLocks.intern("name")) {
return work.call();
}
}
如果线程1(苹果,橙子,香蕉)和线程2(苹果,桃子)可以同时处理(例如)“橙子”和“桃子”,这将起作用。在这种情况下,'work'变量独立地代表'oranges'的工作。
如果线程2必须等到线程1完成所有三个项目之后再启动它们,那么它有点复杂但仍然可以管理。在这种情况下,“工作”代表'苹果+橘子+香蕉'。
public <T> T doWork(List<String> names, Callable<T> work) {
// important to avoid deadlocks
names = new ArrayList<>(names);
Collections.sort(names);
return doWorkInternal(names.iterator());
}
private <T> T doWorkInternal(Iterator<String> names, Callable<T> work) {
if(names.hasNext()) {
synchronized(namedLocks.intern(names.next())) {
return doWorkInternal(names, work);
}
} else { // no more locks to acquire
return work.call();
}
}
在上面的内容中,当你递归堆栈时,你将按排序顺序获取每个锁(对死锁很重要)。
答案 2 :(得分:0)
public class FruitRunnable implements Runnable {
// this is the actual lock
private static final Object lock = new Object();
// here we store which objects are currently used
private static final Set<String> usedObjects = new HashSet<String>();
// these are the objects a thread will need
private final String[] neededObjects;
public FruitRunnable(String... neededObjects) {
this.neededObjects = neededObjects;
}
@Override
public void run() {
acquireLock(neededObjects);
// between these two methods we can assure that there is
// no other thread working on our resources
work();
// important! release the resources afterwards!
releaseLock(neededObjects);
}
private void work() {
System.out.println("working: " + Arrays.toString(neededObjects));
try {
// work of 10 seconds
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void acquireLock(String[] fruits) {
// go into lock
synchronized (lock) {
// loop until we can acquire our resources
boolean success = false;
while (!success) {
success = true;
for (String s : fruits) {
if (usedObjects.contains(s)) {
// too bad this fruit is already in use
success = false;
}
}
// on success add all used fruits to the usedObjects Set and return
if (success) {
for (String s : fruits) {
usedObjects.add(s);
}
return;
}
// if we had no success we will wait until some other thread
// releases fruits
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void releaseLock(String[] fruits) {
synchronized (lock) {
// release the fruits and notify the other threads to re-check
for (String s : fruits) {
usedObjects.remove(s);
}
lock.notifyAll();
}
}
public static void main(String[] args) throws InterruptedException {
// starting the threads from your example
new Thread(new FruitRunnable("apple", "oranges", "bananas")).start();
new Thread(new FruitRunnable("apple", "peaches")).start();
Thread.sleep(2000);
new Thread(new FruitRunnable("guava", "pineapple")).start();
}
}
在代码中查找有关它如何工作的一些评论。