让线程读取共享变量直到修改

时间:2013-11-17 23:38:25

标签: java multithreading

我有一个简单的类,从共享对象读取时不需要线程获取锁。但是,如果任何特定线程尝试修改此共享对象,则没有线程可以从该对象读取。贝娄是我班上的一个例子。

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对象在许多线程中共享。同时声明我的列表是最终静态不是一个好主意,因为列表太大而无法重新创建和设置。

3 个答案:

答案 0 :(得分:2)

你根本不应该返回列表,因为你无法告诉调用者将使用它做什么。您应该自己提供所有列表操作,并根据需要使用锁定。或者通过Colllections.unmodifiableList()将列表提供为只读,并仅提供更新操作。如果你真的认为争用会成为一个问题,那就是java.util.concurrent.locks.ReadWriteLock的情况。

答案 1 :(得分:1)

这种模式本质上是不安全的,因为它仍然可能导致一个线程中posts的更新只在另一个线程中被部分读取。例如,想象一下这种情况:

  1. 线程-A调用getPosts()并获取列表的副本。让我们说它永远不会修改它 - 让我们甚至说你把它包裹在Collections.unmodifiableList中以确保它永远不会被修改。
  2. 线程B调用addPost(somePost)
  3. Thread-A使用从上面第一步获得的引用。
  4. 在这种情况下,在步骤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();
        }
    }