我想编写一个简单的线程安全arraylist,它支持:
add(),remove(int i),insert(int i),update(int i)和get(int i)
一个简单的实现是向内部数据结构添加锁(例如,一个对象数组),但它不够好,因为一次只有一个线程可以访问该列表。
因此,我的初步计划是为每个数据槽添加锁定,以便不同的线程可以同时访问不同索引中的元素。数据结构如下所示:
class MyArrayList {
Lock listlock;
Lock[] locks;
Object[] array;
}
如果不需要调整大小(),则锁定应该如下:
我的问题是:
答案 0 :(得分:0)
一个简单的方法是使用read-write lock([Reentrant]ReadWriteLock
),这么多线程可以同时读取,但一旦有人获得写锁定,其他人就无法访问该列表。
或者你可以做一些与你的想法有些相似的事情:每个插槽一个读写锁+一个全局("结构")读写锁+一个跟踪{{1案件。所以:
j >= i
变量来指示其中的所有位置是否被锁定" (int modifyingFrom
个案)。设置j >= i
后,您将降级(请参阅docs)从写入锁定读取锁定,让其他人访问该列表。modifyingFrom
的当前值冲突。如果发生冲突,请一直睡到设置modifyingFrom
的线程完成并通知所有正在等待的人。必须同步此检查(仅在某个对象上使用modifyingFrom
),因此在冲突线程调用synchronized (obj)
并永久休眠(保持)之前,结构更改线程不会发生obj.notify()
全局读锁!)。 :(obj.wait()
或将boolean structuralChangeHappening = false
设置为某个modifyingFrom
(然后您可以检查x > <list size>
到{{1} }或i < modifyingFrom
)。完成结构更改的线程将get()
设置回此值,此处必须同步以通知等待线程。trimToSize()
或某个东西)数组将在整个操作期间保存全局 write lock < / em>的我很想将全局读写锁视为非常必要,但最后两点证明了这一点。
一些示例案例:
update()
(每个线程都带有modifyingFrom
,唯一或不唯一):每个线程都会获得全局读锁定,然后{ {1}}读取锁定,然后读取位置,没有人会等待。get(i)
的情况相同:如果没有相等的i
,则无人等待。否则,只有线程写入或读取冲突位置的线程才会等待。i
启动update([index =] i, element)
,其他线程尝试i
: t
设置insert([index =] 5, element)
后发布全局写锁,所有读取的线程获取全局读锁,然后检查get(i)
。那些t
的人只能获得插槽的(读)锁;其他人等到modifyingFrom = 5
完成并通知,然后锁定插槽。modifyingFrom
并需要分配一个新数组:一旦获得全局写锁定,其他任何人都无法做任何事情,直到它完成。i < modifyingFrom
调用insert(5)
,另一个线程add()
调用t_a
:
add(element)
碰巧首先获得全局写锁定,它会设置t_g
,一旦释放锁定,get([index =] 7)
获取全局读锁定,就会看到{{1}睡觉直到t_a
完成并通知它。modifyingFrom = 7
首先获得全局读锁定,它将检查t_g
(index (= 7) >= modifyingFrom
,示例前的第4个点),然后抛出异常,因为t_a
释放锁定后!然后t_g
能够获得全局写锁定并正常进行。记住必须同步对7 < modifyingFrom
的访问。
你说你只想要那五个操作,但是如果你想要一个迭代器,它可以通过其他方式(不是迭代器本身)来检查是否有某些东西被改变了,就像标准类一样。
现在,我不知道在哪种条件下,这比其他方法更好。另外,请考虑在实际应用程序中可能需要更多限制,因为这应该只确保一致性:如果您尝试读取和写入相同的位置,则可以在写入之前或之后进行读取。也许有一些像modifyingFrom > <list size> (== 7)
这样的方法是有意义的,只有在调用方法时没有发生冲突的结构变化时才会这样做,或7 >= <list size>
只有在列表位于满足谓词的状态(应该仔细定义,不要导致死锁)。
如果我错过了什么,请告诉我。可能有很多角落案件。 :)