简体   繁体   English

通过参数同步Java方法

[英]Synchronize Java methods by the arguments

Assume that we have a method doSomething(String input) and we want to run it synchronized by different inputs. 假设我们有一个方法doSomething(String input) ,我们想通过不同的输入来同步运行它。

It means that running doSomething(A) should block any consecutive calls of doSomething(A) until the first one is completed BUT should not block doSomething(B) or doSomething(C) . 这意味着运行doSomething(A)应该阻止任何连续调用doSomething(A)直到第一个完成,但不应该阻止doSomething(B)doSomething(C)

So i've created a wrapper method to achieve this goal. 因此,我创建了一个包装器方法来实现此目标。 It creates objects based on input value and places locks on them and keeps a reference to them in a list. 它根据输入值创建对象并对其进行锁定,并在列表中保留对其的引用。

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);
    }
}

It actually works; 它实际上有效; but it's not a totally thread-safe solution as ArrayList is not thread-safe. 但这不是完全线程安全的解决方案,因为ArrayList不是线程安全的。 The contents of ArrayList is not volatile and according to documentation, adding and removing items on the list is not reflected on other threads instantly. ArrayList的内容不是易变的,根据文档,添加和删除列表中的项目不会立即反映在其他线程上。

Note that this implementation is not synchronized. 请注意,此实现未同步。 If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. 如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。

A well known thread safe variant of ArrayList is CopyOnWriteArrayList (which makes a copy of the elements and re-sets the inner volatile field holding elements to be sure to have latest version of the list on all other threads instantly). ArrayList一个众所周知的线程安全变体是CopyOnWriteArrayList (它复制元素并CopyOnWriteArrayList内部volatile字段保存元素,以确保立即在所有其他线程上获得列表的最新版本)。 As the name yields, it COPIES each one of the list items on adding a new item to the list and it means the references to the actual objects that got the lock on, would be lost and the code will be broken. 顾名思义,在将新项目添加到列表时,它会复制每个列表项目,这意味着对获得锁定的实际对象的引用将丢失,并且代码将被破坏。

So I need a datatype which holds a list of objects and doesn't copy or change objects in favor of concurrency support. 因此,我需要一个数据类型,该数据类型包含一个对象列表,并且不为了支持并发而复制或更改对象。 I could use String.intern() functionality but it brings a lot of potential problems especially on mobile platforms. 我可以使用String.intern()功能,但是它带来了很多潜在的问题,尤其是在移动平台上。

Any ideas? 有任何想法吗?

Or, do you know any bulletproof implementations available to use? 或者,您知道可以使用任何防弹实施吗?

I'm on Android platform by the way. 顺便说一下,我在Android平台上。

UPDATE: 更新:

I've reached a solution by myself. 我一个人解决了。 Check out my own answer. 看看我自己的答案。 Comments are welcomed. 欢迎发表评论。

You don't need a data type. 您不需要数据类型。 You need a semaphore dedicated to the locking code. 您需要专用于锁定代码的信号灯。 Take a semaphore before you check contains. 在检查包含内容之前先获取一个信号量。 Figure out if you're in the list. 确定您是否在列表中。 Add yourself to the list if needed. 如果需要,将自己添加到列表中。 Release it after. 之后将其释放。 You now have a synchronous way of obtaining any lock. 您现在有了一种获取任何锁的同步方式。

(This is also basically what any synchronized type will do- except by doing the semaphore yourself you can synchronize multiple operations at a time, such as checking contains then adding to the list. Its a frequent bug to use a synchronized data type, expect it will fix your problems, only to find that you needed to synchronize at a higher level around multiple functions). (这基本上也是任何同步类型都将执行的操作,除非您自己进行信号量操作,否则您一次可以同步多个操作,例如检查包含然后添加到列表。这是使用同步数据类型的常见错误,期望它将解决您的问题,只是发现您需要围绕多个功能进行更高级别的同步)。

Of course your code has other weaknesses. 当然,您的代码还有其他弱点。 You're never removing old keys from the list as they finish, so you could have unbounded size there. 完成操作后,您永远都不会从列表中删除旧键,因此那里的大小可能没有限制。 Also contains on a list is an N operation, a hashmap is better if you care more about speed than memory. 列表中还包含一个N操作,如果您更关心速度而不是内存,那么哈希图会更好。

Unless I misunderstand your question, this is a trivial thing to do: 除非我误解了您的问题,否则这是一件微不足道的事情:

<T, R> R doSomethingSynchronizedByInput(T input, Function<T, R> fn) {
    synchronized(input) { return fn.apply(input); }
}

Amended to note lack of specificity in question and to add two solutions: 进行了修订,以注意缺乏具体的问题,并添加了两个解决方案:

To do the same thing, on the class/type of the input, you need only slightly modify the code above: 要执行相同的操作,在输入的类/类型上,只需稍微修改上面的代码即可:

<T, R> R doSomethingSynchronizedByInput(T input, Function<T, R> fn) {
    synchronized(input.getClass()) { return fn.apply(input); }
}

Finally, doing something similar for values that are .equals (this is, apparently, what you mean by "value") is slightly more complex. 最后,对.equals值的值执行类似的.equals (显然,这就是“值”的意思)稍微复杂一些。 Something along these lines should work: 遵循以下原则应该可以:

// 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); } 
            }
        }
    }
}

Note that there are all kinds of issues, here, that you are not specifying. 请注意,这里没有指定各种问题。 Several people have brought up the issue that it may be dangerous to lock on a public object (because someone else might). 有几个人提出了一个问题,即锁定公共对象可能很危险(因为其他人可能会这样做)。 That might be problem, or it might be a design goal. 那可能是问题,或者可能是设计目标。 Of much more concern, to me, is that this solution is blocking threads instead of queuing tasks. 对我而言,更令人担忧的是,该解决方案正在阻塞线程而不是对任务进行排队。 A blocked thread is a pretty expensive way to represent queued work. 阻塞线程是表示排队工作的一种非常昂贵的方法。

My suggestion is that you take the solutions above and rethink your problem. 我的建议是您采用上述解决方案并重新考虑您的问题。 But, hey, that's not what you asked. 但是,嘿,那不是您要的。

NOTE: For the solution, skip to the UPDATE part below. 注意:有关解决方案,请跳到下面的UPDATE部分。

As i dived deeper in CopyOnWriteArrayList class, i noticed that CopyOnWriteArrayList.add() uses Arrays.copyOf() which makes a shallow copy of the elements list. 当我深入研究CopyOnWriteArrayList类时,我注意到CopyOnWriteArrayList.add()使用Arrays.copyOf()可以对元素列表进行浅表复制 It means that it only copies the array itself, not the elements inside. 这意味着它仅复制数组本身,而不复制其中的元素。 They're just passed on to the new array. 它们只是传递给新数组。 So, the lock objects stay intact and we can be sure that the object retrieved via 因此,锁定对象保持不变 ,我们可以确定通过

runningTasks.get(runningTasks.indexOf(input))

is exactly the same object we added on runningTasks and locked on, and the list is on it's latest version on all threads immediately after editing anhy of list items. 与我们在runningTasks上添加并锁定的对象完全相同,并且在编辑完列表项后,该列表即为所有线程上的最新版本。

I ran the experiment with structural changes to the list, just to be sure: 为了确保对清单进行结构更改,我进行了实验:

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 

And the results were: 结果是:

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

So the references to original objects would be kept. 因此,将保留对原始对象的引用。 Thus the code can be modified to use CopyOnWriteArrayList . 因此,可以将代码修改为使用CopyOnWriteArrayList The final version would be: 最终版本为:

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);
    }
}

It's not perfect though. 不过,这并不完美。

Feedbacks are welcomed. 欢迎反馈。

UPDATE UPDATE

I managed to implement a better one. 我设法实现了更好的解决方案。 This one is completely thread-safe, prevents race conditions and cleans up memory after usage. 这是完全线程安全的,可防止出现争用情况并在使用后清理内存。

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);
            }
        }
    }

}

Usage: 用法:

You should create a final static field having an instance of this class. 您应该创建一个具有此类实例的最终静态字段。 Use getLockObject() to get the object needed for synchronized block. 使用getLockObject()获取synchronized块所需的对象。 In end of the synchronized block (last line, in finally , before return , etc.), run cleanUpLockObject() to clear memory. 在同步块的末尾(最后一行,在finally ,在return之前,等等),运行cleanUpLockObject()清除内存。 Both methods should be called just once per execution per thread, as calling them changes the thread counter. 每次执行每个线程时,这两种方法应仅被调用一次,因为调用它们会更改线程计数器。

It keeps a track of how many threads are locking on this object, and clears the object if no other threads are locking on it. 它跟踪锁定在此对象上的线程数,并在没有其他线程锁定在该对象时清除该对象。

private static ParameterSynchronizer<String> ps = new ParameterSynchronizer<>();

public void doSomethingSyncedByInput(String input){
    String lockObject = ps.getLockObject(input);
    synchronized (lockObject) {
        doSomething(input);
        ps.cleanUpLockObject(lockObject);
    }
}

And just in case, if doSomething() throws, it could be catched like 以防万一,如果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);
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM