假设我们有一个方法doSomething(String input)
,我们希望通过不同的输入同步运行它。
这意味着运行doSomething(A)
应阻止doSomething(A)
的所有连续调用,直到第一个调用完成为止,但不应阻止doSomething(B)
或doSomething(C)
。
所以我创建了一个包装器方法来实现这个目标。它根据输入值创建对象并对其进行锁定,并在列表中保留对它们的引用。
private static final ArrayList<String> runningTasks = new ArrayList<>();
public void doSomethingSyncedByInput(String input) {
// Create a lock or load an already created lock from the list.
// (Yeah, it's a race condition but forget about it. It's just an example.)
String lock = new String(input);
if(runningTasks.contains(input)){
// get currently available lock object
lock = runningTasks.get(runningTasks.indexOf(input));
}else {
// add a reference on tasks list
runningTasks.add(lock);
}
synchronized (lock) {
doSomething(input);
}
}
它确实有效;但它不是完全线程安全的解决方案,因为ArrayList
不是线程安全的。 ArrayList
的内容不易变,根据文档,添加和删除列表中的项目不会立即反映在其他线程上。
请注意,此实现未同步。如果多个线程同时访问
ArrayList
实例,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。 / p>
ArrayList
的一个众所周知的线程安全变体是CopyOnWriteArrayList
(它会复制元素并重新设置内部volatile
字段保存元素以确保具有最新版本立即在所有其他线程上的列表)。随着名称的产生,在向列表添加新项目时,每个列表项都 COPIES ,这意味着对已经锁定的实际对象的引用将丢失,代码将会丢失被打破了。
所以我需要一个包含对象列表的数据类型,并且不会复制或更改对象以支持并发支持。我可以使用String.intern()
功能,但它会带来很多潜在的问题,特别是在移动平台上。
有什么想法吗?
或者,您知道可以使用的任何防弹实现吗?
顺便说一句,我在 Android 平台上。
我自己也找到了解决方案。看看我自己的答案。欢迎提出意见。
答案 0 :(得分:0)
您不需要数据类型。您需要一个专用于锁定代码的信号量。在检查包含之前获取信号量。弄清楚你是否在列表中。如果需要,请将自己添加到列表中。之后发布。您现在可以通过同步方式获得任何锁定。
(这也基本上是任何同步类型都会做的 - 除了自己做信号量,你可以同时同步多个操作,比如检查包含然后添加到列表。它是使用同步数据类型的常见错误,期望它将解决您的问题,只是发现您需要在更高级别围绕多个功能进行同步)。
当然,您的代码还有其他弱点。你永远不会从列表中删除旧密钥,因此你可以在那里拥有无限大小。列表中还包含一个N操作,如果你更关心速度而不是内存,那么hashmap会更好。
答案 1 :(得分:0)
除非我误解了你的问题,否则这是一件微不足道的事情:
<T, R> R doSomethingSynchronizedByInput(T input, Function<T, R> fn) {
synchronized(input) { return fn.apply(input); }
}
修改后注意到缺乏特异性并添加两个解决方案:
要做同样的事情,在输入的类/类型上,您只需稍微修改上面的代码:
<T, R> R doSomethingSynchronizedByInput(T input, Function<T, R> fn) {
synchronized(input.getClass()) { return fn.apply(input); }
}
最后,对.equals
的值做一些类似的事情(显然,你的意思是“值”)稍微复杂一些。这些方面应该有用:
// Note: I have not tested this: it is just a sketch.
// It requires that type T have an "equals" method that divides
// it into "values"
// Don't try to use a Comparator, because the HashMap doesn't.
public class DoSomethingSynchedByInput<T, R> {
public interface Listener<V> { void accept(V val); }
private final Map<T, LinkedList<T>> waiting = new HashMap<>();
void doSomething(final T input, Function<T, R> fn, Listener<R> listener)
throws InterruptedException {
synchronized (waiting) {
LinkedList<T> waitList = waiting.get(input);
if (waitList == null) { waitList = new LinkedList<>(); }
waitList.addLast(input);
waiting.put(input, waitList);
while (true) {
if (waitList.peekFirst() == input) { break; }
waiting.wait();
}
}
try { listener.accept(fn.apply(input)); }
finally {
synchronized (waiting) {
LinkedList<T> waitList = waiting.get(input);
waitList.getFirst();
if (waitList.size() > 0) { waiting.notifyAll(); }
else { waiting.remove(input); }
}
}
}
}
请注意,此处有各种问题,您没有指定。有几个人提出了锁定公共对象可能是危险的问题(因为其他人可能)。这可能是个问题,也可能是设计目标。对我来说更重要的是,这个解决方案是阻塞线程而不是排队任务。被阻止的线程是表示排队工作的一种非常昂贵的方式。
我的建议是你采取上述解决方案并重新思考你的问题。但是,嘿,那不是你问的。
答案 2 :(得分:0)
注意:对于解决方案,请跳至下面的更新部分。
当我在CopyOnWriteArrayList
课程中深入学习时,我注意到CopyOnWriteArrayList.add()
使用了Arrays.copyOf()
,这使得元素列表的浅拷贝。这意味着它只复制数组本身,而不是里面的元素。他们只是传递给新阵列。 因此,锁定对象保持不变,我们可以确定通过
runningTasks.get(runningTasks.indexOf(input))
与我们在runningTasks
上添加并锁定的对象完全相同,并且在编辑了列表项之后,列表立即显示在所有线程上的最新版本。
我在列表中进行了结构更改的实验,只是为了确定:
CopyOnWriteArrayList<String> l = new CopyOnWriteArrayList<>();
String s1 = new String("foo");
String s2 = new String("bar");
String s3 = new String("bar"); // Different object, same value
l.add(s1);
Log.e("TEST", "Result: "+String.valueOf( s1 == l.get(0) ));
l.add(s2);
Log.e("TEST", "Result: "+String.valueOf( s1 == l.get(0) ));
Log.e("TEST", "Result: "+String.valueOf( s2 == l.get(1) ));
l.add(s3);
Log.e("TEST", "Result: "+String.valueOf( s1 == l.get(0) ));
Log.e("TEST", "Result: "+String.valueOf( s2 == l.get(1) ));
Log.e("TEST", "Result: "+String.valueOf( s3 == l.get(2) ));
l.remove(1); // the s2
Log.e("TEST", "Result: "+String.valueOf( s1 == l.get(0) ));
Log.e("TEST", "Result: "+String.valueOf( s2 == l.get(1) )); // should be false
Log.e("TEST", "Result: "+String.valueOf( s3 == l.get(1) )); // should be true
结果是:
E/TEST: Result: true
E/TEST: Result: true
E/TEST: Result: true
E/TEST: Result: true
E/TEST: Result: true
E/TEST: Result: true
E/TEST: Result: true
E/TEST: Result: false
E/TEST: Result: true
因此将保留对原始对象的引用。因此,可以修改代码以使用CopyOnWriteArrayList
。最终版本将是:
private static final CopyOnWriteArrayList<String> runningTasks = new CopyOnWriteArrayList<>();
public void doSomethingSyncedByInput(String input) {
String lock;
int index;
synchronized (runningTasks) {
index = runningTasks.indexOf(input);
if (index >= 0) {
// get currently available lock object
lock = runningTasks.get(index);
} else {
// add a reference on tasks list
lock = new String(input);
runningTasks.add(lock);
}
}
synchronized (lock) {
if(!runningTasks.contains(lock)){
runningTasks.add(lock);
}
doSomething(input);
index = runningTasks.indexOf(lock);
if(index >= 0)
runningTasks.remove(index);
}
}
但是,它并不完美。
欢迎提供反馈。
我设法实现了更好的一个。这个是完全线程安全的,可以防止竞争条件并在使用后清理内存。
public class ParameterSynchronizer <T> {
private final CopyOnWriteArrayList<T> objects;
private final ConcurrentHashMap<T, Integer> lockCounter;
public ParameterSynchronizer(){
objects = new CopyOnWriteArrayList<>();
lockCounter = new ConcurrentHashMap<>();
}
public T getLockObject(T input){
synchronized (objects) {
T lock = input;
int index = objects.indexOf(lock);
if (index >= 0) {
lock = objects.get(index);
lockCounter.put(lock, lockCounter.get(lock)+1);
} else {
objects.add(lock);
lockCounter.put(lock, 1);
}
return lock;
}
}
public void cleanUpLockObject(T input){
synchronized (objects) {
T lock = input;
int counter = lockCounter.get(lock);
if(counter == 1) {
objects.remove(objects.indexOf(lock));
lockCounter.remove(lock);
}else{
lockCounter.put(lock, counter - 1);
}
}
}
}
<强>用法:强>
您应该创建一个具有此类实例的最终静态字段。使用getLockObject()
获取synchronized
阻止所需的对象。在同步块的末尾(最后一行,finally
,return
之前等),运行cleanUpLockObject()
以清除内存。每个线程每次执行都应该调用两个方法,因为调用它们会改变线程计数器。
它跟踪锁定此对象的线程数,并在没有其他线程锁定时清除该对象。
private static ParameterSynchronizer<String> ps = new ParameterSynchronizer<>();
public void doSomethingSyncedByInput(String input){
String lockObject = ps.getLockObject(input);
synchronized (lockObject) {
doSomething(input);
ps.cleanUpLockObject(lockObject);
}
}
以防万一,如果doSomething()
抛出,可能会像
private static ParameterSynchronizer<String> ps = new ParameterSynchronizer<>();
public void doSomethingSyncedByInput(String input) throws Exception {
String lockObject = ps.getLockObject(input);
synchronized (lockObject) {
try {
doSomething(input);
} catch(Exception e) {
throw e;
} finally {
ps.cleanUpLockObject(lockObject);
}
}
}