有什么理由不在任何地方打“同步”关键字吗?

时间:2009-06-11 03:01:08

标签: java multithreading synchronization

在我的java项目中,我写的几乎所有非静态方法都是synchronized。我已经决定通过删除大多数synchronized关键字来修复一些代码。就在那里,我创建了几个线程问题,需要花费很长时间来修复,而不会增加性能。最后,我还原了一切。

我没有看到其他人在任何地方编写带有“synchronized”的代码。那么,我不应该在任何地方都有“synchronized”吗?

如果我不太关心性能(即,该方法每隔几秒不会调用一次以上),该怎么办?

9 个答案:

答案 0 :(得分:36)

如果您不加选择地进行同步,则还存在创建deadlock的风险。

假设我有两个类FooBar,两个类都有同步方法doSomething()。进一步假设每个类都有一个synchronized方法,将另一个类的实例作为参数。

public class Foo {

    synchronized void doSomething() {
        //code to doSomething
    }

    synchronized void doSomethingWithBar(Bar b) {
        b.doSomething();
    }

}

public class Bar {

    synchronized void doSomething() {
        //code to doSomething
    }

    synchronized void doSomethingWithFoo(Foo f) {
        f.doSomething();
    }
}

你可以看到,如果你有Foo的实例和Bar的实例,两者都试图同时在彼此之间执行doSomethingWith*方法,那么死锁就可以了发生。

要强制死锁,您可以在doSomethingWith*方法中插入一个睡眠(以Foo为例):

synchronized void doSomethingWithBar(Bar b) {
    try {
        Thread.sleep(10000);
    } catch (InterruptedException ie) {
        ie.printStackTrace();
    }

    b.doSomething();
}

在main方法中,您启动两个线程来完成示例:

public static void main(String[] args) {
    final Foo f = new Foo();
    final Bar b = new Bar();
    new Thread(new Runnable() {
        public void run() {
            f.doSomethingWithBar(b);
        }
    }).start();

    new Thread(new Runnable() {
        public void run() {
            b.doSomethingWithFoo(f);
        }
    }).start();
}

答案 1 :(得分:27)

当然 - 表现。监视器需要付费。

答案既不是删除也不是以随机方式添加同步。最好阅读Brian Goetz的“Java Concurrency In Practice”或Doug Lea的“Java Threads”等书籍,以了解如何正确地完成它。当然,要好好学习新的并发软件包。

多线程比synchronized关键字要多得多。

答案 2 :(得分:13)

如果您认为在任何地方放置“synchronized”关键字是一个很好的解决方案,即使忽略了性能,那么您也无法准确理解发生了什么,也不应该使用它。我强烈建议在没有指导的情况下深入研究这个主题。

线程是一个非常难以掌握的主题,理解你为什么要做的事情非常重要。否则你将会遇到很多代码而不是设计的代码。这最终会让你感到痛苦。

答案 3 :(得分:7)

表现点已经说明了。

另外,请记住,所有线程都会获得堆栈的不同副本。因此,如果方法仅使用在该方法内创建的变量,并且无法访问外部世界(例如文件句柄,套接字,数据库连接等),则不会出现线程问题。

答案 4 :(得分:6)

比将synchronized放在任何地方要好得多的是仔细推理你的类需要的不变量,然后同步就足以确保那些不变量。如果过度同步,则会产生两种风险:

  1. 死锁
  2. 活跃问题
  3. 每个人都知道死锁。活动问题与同步成本无关,而是与全局同步多线程应用程序中的所有内容相关的事实,然后许多线程将被阻塞等待获取监视器,因为另一个线程正在触及不相关的东西但使用相同的东西监视。

    如果您想在任何地方拍一个关键字以确保安全,那么我建议您使用final代替synchronized:)您可以做出的任何事情final都有助于提高线程的安全性,并更容易推理锁定需要维护的不变量。举个例子,假设你有这个琐碎的课程:

    public class SimpleClass {
        private volatile int min, max;
        private volatile Date startTime;
        // Assume there are many other class members
    
        public SimpleClass(final int min, final int max) {
            this.min = min;
            this.max = max;
        }
        public void setMin(final int min) {
            // set min and verify that min <= max
        }
        public void setMax(final int max) {
            // set max and verify that min <= max
        }
        public Date getStartTime() {
            return startTime;
        }
    }
    

    使用上面的类,设置min或max时,需要同步。上面的代码被破坏了。这门课有什么不变量?您需要始终确保min <= max,即使多个线程正在调用setMinsetMax

    假设这是一个包含许多变量的大类并且您同步所有内容,那么如果一个线程调用setMin而另一个线程调用getStartTime,则第二个线程将被不必要地阻塞,直到setMin回报。如果你用一个有很多成员的类来做这个,并且假设只有少数成员参与了必须保护的不变量,那么同步所有内容将导致许多这类活跃问题。

答案 5 :(得分:2)

只是它几乎肯定会减慢速度。

每个sync'ed对象必须执行某种检查以确保它尚未被使用,然后在使用时设置标志并在完成时重置它们。

这需要时间。您可能不会注意到功能上的变化,但如果性能很重要,则需要注意。

答案 6 :(得分:2)

你不应该,你可能需要重新考虑你的设计。根据我的经验,这个级别的同步只是不可扩展。它可能会解决您的直接问题,但无法帮助您修复整体应用程序的完整性。

例如:即使你有一个“同步”集合,一个线程可能想要一次调用两个“同步”方法,但不希望另一个线程看到中间结果。因此,细粒度“同步”API的任何调用者都必须自己意识到这一点,这严重降低了此API的(程序员)可用性。

首先,最好使用工作线程。隔离特定的长时间运行的任务,以便单独的线程获取一个不可变输入块,并在完成时将结果排入主线程的消息队列(这当然应该是同步的)。如果没有别的办法,主线程可以轮询或明确地等待信号。具体细节取决于您的性能要求和应用程序设计。

从长远来看,尝试理解数据库实现或消息传递并发的ACID事务模型的实现,例如,Erlang语言将为您提供很多灵感。这些是经证明可扩展的同步模型。充分利用它们并尝试将这些概念应用于您所使用的多线程应用程序。

答案 7 :(得分:1)

假设你有这个例子


synchronized (lock){
    doSomething();
}

如果您锁定了多个线程,则无法中断它们。最好使用 Lock 对象而不是“synchronized”,因为您可以更好地控制可能发生的中断。 当然,像所有建议的那样,“同步”也存在一些性能问题,如果你过度使用它们,你可能会遇到死锁,活锁等。

答案 8 :(得分:0)

如果性能不是太大的问题,为什么不使用一个线程然后你可以安全地删除所有“同步”如果你同步所有你有效实现相同的事情只有一个线程可以做任何事情,但是以更加复杂和容易出错的方式。

问问自己,为什么使用多个线程?