【问题标题】:How should I properly block access from other threads to this object?我应该如何正确阻止其他线程对该对象的访问?
【发布时间】:2013-11-17 20:32:55
【问题描述】:

我自己正在做这个小练习,试图了解我应该如何使用并发和线程。

有时我有一个我不能修改它的源代码并且不是线程安全的对象,所以我希望它只被一个线程访问。

在此示例中,我无法触摸的第三方对象称为 Holdeable。我所做的是尝试将它包装到一个名为 Holder 的类中,该类具有同步方法,并且我希望通过这样做只有一个线程可以访问该 Holdeable 对象。有时我将对 Holdeable 对象的引用设置为空,并且我希望它正确地完成,以便当其他线程评估 mHolder.getHoldeable()==null 为真时,并避免输入可能导致 NullPointerException 的代码。

我的最后一次尝试包括一个同步块,是这样的:

class Holder {
    Holdeable mHoldeable;
    public synchronized void setHoldeable(Holdeable holdeable) { mHoldeable = holdeable; }
    public synchronized Holdeable getHoldeable() { return mHoldeable; }

}

class Holdeable { // Cannot be modified, that would be to cheat :D
    public int someValue;

}
public class MainClass {
    private static Holder mHolder;


    public static void main(String[] args) {
        try {
            Holdeable holdeable = new Holdeable();
            mHolder = new Holder();
            mHolder.setHoldeable(holdeable);

            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        while(true) {   
                            synchronized(mHolder) {
                                if(mHolder.getHoldeable() != null) {
                                    Thread.sleep(23);
                                    System.out.println(mHolder.getHoldeable().someValue);
                                } else {
                                    System.out.println("No holder!"); 
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

            Thread.sleep(1000);
            mHolder.getHoldeable().someValue = 2;
            Thread.sleep(1500);
            mHolder.getHoldeable().someValue = 3;
            Thread.sleep(500);
            mHolder.setHoldeable(null);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


    }



} 

此示例避免抛出 NullPointerException,但正如您所见,执行如此多的锁定只需要很多时间。当我阅读“并发的艺术”一书时,我正在努力解决这个问题,看看我是否终于明白了。

你怎么看?

【问题讨论】:

    标签: java multithreading concurrency


    【解决方案1】:

    您的代码不是线程安全的。您有两个级别的锁定:

    1. Holder 的方法是同步的;
    2. 您在 Holder 实例上进行同步。

    第 1 点没有为您提供足够的同步,因为它仅涵盖 fetching 可持有且不访问其属性;

    第 2 点根本没有给您任何同步,因为您只在一个线程中获取锁。

    我建议您只使用第 2 点。并始终如一地应用它。

    顺便说一句,您的程序需要很长时间才能执行,因为它调用了Thread.sleep。锁定的性能太高了,如果不涉及重复至少数十万次的紧密循环,您就无法注意到它。

    【讨论】:

    • 我不认为我理解你。您说第 1 点和第 2 点都没有正确同步,那为什么我只使用第 2 点呢?你能更好地解释一下吗?谢谢:)
    • 您应该使用第 2 点中的 方法,但通过在所有线程中获取锁来正确实施。但是你根本不需要包装器对象。
    • 哦,我知道你是对的。我还没有通过同步块获得主锁中的锁。所以这在没有包装器的情况下工作,我实际上锁定了可持有的实例。但是后来我意识到我不能只获取实例的锁而不先检查它是否为空。然后我将有两个 if 语句检查该实例引用的非空值。还是有问题:/谢谢!
    • 这很容易解决:创建一个public static final Object lock = new Object() 并将其用于锁定。
    【解决方案2】:
    public class Holder {
      final Holdeable mHoldeable;
    
      Holder(Holdeable holdeable) {
        this.mHoldeable = Objects.requireNonNull(holdeable, "Holdeable cannot be null");
      }
    
      Holdeable get() {
        return mHoldeable;
      }
    }
    

    使用此构造将使您的生活变得更加轻松。您可能已经注意到:它也消除了同步的需要。如果我写了一本书,那会在第一页。 ;)

    线程和同步在架构级别上几乎 100% 完成,添加一些同步块只是一种备份/快速和肮脏的解决方案。

    【讨论】:

    • 对不起,我不明白。您究竟如何避免一次从多个线程修改 Holdeable 实例的属性?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多