注意:对于解决方案,请跳到下面的更新部分。
当我深入了解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);
}
}
}
}
用法:
您应该创建一个具有此类实例的 final static 字段。使用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);
}
}
}