我有一个简单的类,从共享对象读取时不需要线程获取锁。但是,如果任何特定线程尝试修改此共享对象,则没有线程可以从该对象读取。贝娄是我班上的一个例子。
package com.taylor.wall.core.domain;
import java.util.List;
public class Wall {
// This needs to be synchronized and concurrent
private List<Post> posts;
private final Object lock = new Object();
public void addPost(Post post) {
synchronized (lock) {
posts.add(post);
}
}
// Unfortunately this is wasteful, because for threads must now acquire lock before reading
public List<Post> getPost() {
synchronized (lock) {
return posts;
}
}
}
什么是更好的选择? wall对象在许多线程中共享。同时声明我的列表是最终静态不是一个好主意,因为列表太大而无法重新创建和设置。
答案 0 :(得分:2)
你根本不应该返回列表,因为你无法告诉调用者将使用它做什么。您应该自己提供所有列表操作,并根据需要使用锁定。或者通过Colllections.unmodifiableList()
将列表提供为只读,并仅提供更新操作。如果你真的认为争用会成为一个问题,那就是java.util.concurrent.locks.ReadWriteLock
的情况。
答案 1 :(得分:1)
这种模式本质上是不安全的,因为它仍然可能导致一个线程中posts
的更新只在另一个线程中被部分读取。例如,想象一下这种情况:
getPosts()
并获取列表的副本。让我们说它永远不会修改它 - 让我们甚至说你把它包裹在Collections.unmodifiableList
中以确保它永远不会被修改。addPost(somePost)
。在这种情况下,在步骤3中,我们有一个列表,该列表已在一个线程(线程B)中更新,并在另一个线程(线程A)中读取而没有任何内存同步。您可以在posts
列表中,在其包含的任何一个Post
对象中,在其中一个Post
对象引用的任何对象中读取部分更新等。
您的两个选择是:
List
实现。List
的新副本。如果你这样做,你可以:
addPost
上创建这些新列表(并从Collections.unmodifiedList
返回包含在getPosts
中的新列表,以及我在EJP答案评论中提到的同步) ;或不幸的是,线程安全列表的方式并不多。您可以使用CopyOnWriteArrayList
,(顾名思义)在每次写入时创建内部副本,因此类似于上面提到的第一个副本变体。您还可以使用原始posts
列表并将其包装在Collections.synchronizedList
中。如果你这样做,你应该确保没有其他人拥有预先包装的posts
列表的副本。如果他们可能(也就是说,如果你将列表传递给你的构造函数),那么你应首先制作该列表的副本,然后将其包装在Collections.synchronizedList
中,然后将其作为unmodifiableList
返回来自getPosts
。如果采用这种方法,您甚至不需要自己进行同步!
private final List<Post> posts;
public Wall(List<Post> posts) {
this.posts = Collections.synchronizedList(new ArrayList<>(posts));
}
public void addPost(Post post) {
posts.add(post); // internally synchronized
}
public List<Post> getPosts() {
return Collections.unmodifiableList(post);
}
如果您这样做,请确保读者线程知道可以随时修改列表。这意味着它无法检查条件然后对其采取行动,除非它锁定了该列表:
if (!posts.isEmpty() {
// another thread could clear the thread right now!
posts.get(0); // could fail
}
如果您只是添加帖子(即,Wall.removePost
之类的方法不是这样),那么您不必担心这一点。
答案 2 :(得分:0)
java.util.concurrent包中有一个ReadWriteLock,可用于进行独占写入和非独占读取。
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public void addPost(Post post) {
lock.writeLock().lock();
try {
posts.add(post);
} finally {
lock.writeLock().unlock();
}
}
public List<Post> getPost() {
lock.readLock().lock();
try {
//You did not tell us what type of list posts is or what actually happens to it.
//Just locking around returning the pointer only forces other threads to see the updates
//you've made. It doesn't stop them from then attempting unsafe things on it after it's
//returned.
return Collections.unmodifiableList(new ArrayList<Post>(posts));
} finally {
lock.readLock().unlock();
}
}