如何在 java 中请求锁定数据结构的数据

How to ask for lock on data of a datastructure in java

最近,在研究现代数据库中的事务时,我发现现在的事务不对整个集合或table使用锁或监视器,但它们通常对集合的数据或table 将由事务执行的不同操作使用。

所以我在想,假设我们有一个数据结构,它可以是一个 LinkedList,一个 Hashtable 等,并且多个不同的事务想要同时访问该结构以更新数据。我如何请求锁定事务将要使用的数据而不是整个对象数据结构?这显然会提高性能,因为使用相同数据结构的不同数据的不同更新事务将同时发生。

我会尽量阐明我想要实现的目标,这里是一个例子:

public class Table {

    // suppose this is the data of the table and it has a lot of values
    private LinkedList<String> data = new LinkedList();

    public void doTransactionJob() {

        // here we get the data from the list
        // and we ask for monitor on this data so that no other
        // transaction can operate on it
        synchronized(data.get(randomIndex)) {
            // here the transaction works on the data but doesnt
            // block any other transaction from working on the same table
            // but with different data
        }
    }

}

是否存在与我上面的示例类似的内容?

您可以使用 SynchronizedList 对象,它会阻止和同步列表上的任何操作,甚至是读取操作,因此最好在添加或更新列表时使用,或者 CopyOnWriteList将使用新对象创建一个新列表,并且不会对 运行 的读取操作造成问题。 但是如果你的问题是对象本身在列表中的某个位置,我建议在你想要阻止的方法上使用 ReentrantReadWriteLock 策略,这样你可以更好地管理锁定,只有当人们更新对象或其他东西时, 甚至使用 StampedLock.

Here you can see how use Java locks. What you say with "lock the data the transaction is using" is very vague, you can't block an entire object AFAIK, you can block pieces of that object, that only one thread can pass through at time. Anyway if you don't want to use locks on the class methods, you can create a second list, a list of object ID's that are been used, and can't be get by other transaction, you can combine this with a lamport bakery algorithm,或将需要该对象的事务添加到队列的简单代码;

List<Object> objects = new ArrayList<>();
List<Integer> objectIds = new ArrayList<>();
Queue<Integer> transactionIds = new PriorityQueue<>();
// code ...
// suppose this is a transaction that run on a thread, and can access our lists
public void doUpdateOnObject() {
    Object object = objects.get(1);
    if (objectsIds.indexOf(object) > 0) {
        transactionIds.add(this.id) // this transactionId
        while(transactionIds.peek() != this.id) { // if you aren't the next, wait    
            Thread.sleep(400);
        }
    }
    objectIds.add(object.getId());
    // here change object fields or whatever;
    objectIds.remove(object.getId());
}

实际上你的例子工作正常,因为它使用你想要锁定的对象作为监视器,所以当一个线程在同步块内时,没有其他线程可以访问它。

在我对这个问题进行大量推理之后,@Kaneda 和@user207421 提出了一个解决方案:

public class Table {

    // suppose this is the data of the table and it has a lot of values
    private LinkedList<String> data = new LinkedList();

    public void doTransactionJob() {

        // here we get the data from the list
        // and we ask for monitor on this data so that no other
        // transaction can operate on it
        synchronized(data.get(randomIndex)) {
            // here the transaction works on the data but doesnt
            // block any other transaction from working on the same table
            // but with different data
        }
    }

}

这是因为 @Kaneda 写的评论

It returns the value of the object reference, if a get operation on a DS give only the value, you would never be able to update the object state in a list, and need to set it in that position the new object value again.

因此,从列表中获取值并在其上请求监视器似乎完全正常。