0

我正在开发一种池机制。池中的对象将被不同的线程使用。happens-before因此,我需要保证在访问这些对象的非最终属性时会有关系。

由于给定对象一次只能由一个线程访问,因此我不需要保证任何原子性。happens-before当只需要实现时,什么更优化?每次读取或修改非最终属性之一时,要在最终属性上使用同步块?或者使用并发类,例如ConcurrentMap

(我不是针对池本身的属性问这个问题,这显然需要提供原子性;我只是针对从池中获得的对象的属性问这个问题)

4

4 回答 4

3

每次读取或修改非最终属性之一时,要在最终属性上使用同步块?还是使用并发类,例如 ConcurrentMap?

同步是围绕称为内在锁或监视器锁的内部实体构建的。每个对象都有一个与之关联的内在锁。按照惯例,需要对对象字段进行排他和一致访问的线程必须在访问对象之前获取对象的内在锁,然后在完成访问时释放内在锁。

ConcurrentMapie的一个实现,ConcurrentHashMap使用互斥锁的可重入锁lock(),锁由方法获取并由线程持有,直到调用unlock()方法。虽然,ReentrantLock提供与隐式锁相同的可见性和顺序保证,由 获取synchronized keyword,但它提供了更多功能并且在某些方面有所不同:

  1. ReentrantLock可以通过指定为最长等待线程fairness property提供锁来实现公平,以防发生争用。
  2. 提供方便tryLock()的方法来获取锁,只有当它可用或未被任何其他线程持有时,减少线程等待锁的阻塞。tryLock()如果锁定在特定时间段内不可用,可以使用 with timeout 来超时。
  3. 在这种情况下synchronized keyword,线程可能会被无限期地阻塞等待锁定,并且无法控制它。ReentrantLock提供了一个方法叫做lockInterruptibly(),可以用来在线程等待锁的时候中断线程。

reentrant lock可以从ConcurrentHashMap的内部replace(K key, V oldValue, V newValue)函数实现中展示一个使用示例:

boolean replace(K key, int hash, V oldValue, V newValue) { 
               // called by replace(K key, V oldValue, V newValue)
        lock(); // acquire the lock
        try {
            HashEntry<K,V> e = getFirst(hash);
            while (e != null && (e.hash != hash || !key.equals(e.key)))
                e = e.next;

            boolean replaced = false;
            if (e != null && oldValue.equals(e.value)) {
                replaced = true;
                e.value = newValue;
            }
            return replaced;
        } finally {
            unlock(); // unlock 
        }
    }

还有其他功能,如put(),writeObject(java.io.ObjectOutputStream)等,也使用可重入同步实现ReentrantLock,没有它,同步代码将不得不采取许多额外的预防措施,以避免线程导致自身阻塞。这就是为什么我认为你的情况ConcurentMap更可取。

参考:

  1. 内在锁和同步
  2. 可重入锁类
  3. synchronized 与 ReentrantLock 之间的区别
  4. 并发哈希映射
于 2013-10-13T00:48:10.280 回答
2

由于给定对象一次只能由一个线程访问,因此我不需要保证任何原子性。

如果我理解正确,您的意思是您的对象将在线程间共享,但实际上没有并发(对象永远不会被多个线程同时访问)。您想知道需要什么同步。

我一直在想同样的问题,我的结论如下:如果对象 Y 同步对对象 X 的访问,则对象 X 本身不需要同步。在池的情况下,您可能使用同步的方法获取和释放对象,因此它们保护池中的对象。

让我们考虑池中的对象 O。池有两种方法Object acquire(),并且release(Object o)是同步的。要将对象 O 从线程 T1 传递给 T2,T1 必须先释放 O,然后 T2 必须获取 O。T1 的释放和 T2 的获取之间已经存在发生前的关系,因此对象 O 不需要自身同步.

于 2013-10-14T07:00:57.810 回答
0

如果您需要happens-before关系的字段是原语,并且您使用的是Java 5或更高版本,那么您可以使用volatileJava 5+中的关键字确保在读取之前发生。

如果它是一个对象,那么您需要使用某种同步,或者使用synchronized关键字或java.util.concurrent包中的一个类,这通常比简单同步具有更好的性能。例如,通常比它对每个 bin 使用单独的锁ConcurrentHashMap具有更好的性能,因此锁争用更少。Collections.synchronizedMap(Map)

于 2013-10-13T00:16:44.053 回答
-1

好吧,如果你需要保证发生之前,我认为你可以创建一个同步块,保存你想要锁定的对象,然后在这个块中,在使用这个对象之前做你想做的事情,然后调用你的东西从对象,然后释放

例如

public void doSomething(){
    synchronized(myObject){
        doAnotherThing();
        myObject.doStuff();
    }
}

顺便说一句,我之前没有尝试过 ConcurrentMap ......顺便说一下,请澄清你的例子,因为我认为我没有让你正确!

于 2013-10-13T00:01:34.917 回答