原因:过多的同步可能会导致性能降低,死锁,甚至不确定的行为。
为了避免死锁的危险,在一个被同步的方法或是代码块中,永远不要放弃对客户的控制。换句话说,在一个被同步的区域内部,不要调用一个可被改写的公有或是受保护的方法。因为从包含该同步区域的类的角度来看,这样的方法是一个外来者,这个类不知道该方法会做什么事情,也控制不了它。例如,客户可以为这个外来的方法提供一个实现,并且在该方法中创建另外一个线程,再回调到这个类中,然后新建的线程试图获取原线程所拥有的那把锁,这样会导致新建的线程被阻塞,如果创建该线程的方法正在等待这个线程完成任务,则形成了死锁。
同步区域之外被调用的外来方法被称为“开发调用”,除了可以避免死锁以外,开发调用还可以极大增加并发性,外来方法的运行时间可能会任意长。
通常,在同步区域内你应该做尽量少的工作。获得锁,检查共享数据。根据需要转换数据,然后放掉锁。如果你必须要执行某个很耗时的动作,则应该设法把这个动作移到同步区域外面。
性能问题:同步造成性能郁闷的问题很多,就拿JDK来说吧。在1.5以前有StringBuffer这个类和BufferedInputStream类,这些类都是线程安全的。但是说实话,基本上使用这些类都是在单线程中使用的,所以JDK帮他实现的同步是很多时候没有必要。那怎么办啊?我们可以采用StringBuilder来替代StringBuffer,通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。
但是我们在做API的时候如何知道客户调用你的代码是否会设计到线程安全的问题。我们该做成线程安全还是不做?这个是个问题。
下面的原则可以提供给你做选择:
如果你正在编写的类将主要被用到要求同步的环境中,同时也被用于不要求同步的环境中。那么,一个合理的方法是,都提供。
都提供该怎么做呢?一种做法就是提供一个包装类,它实现一个描述该类的接口,同时,在将方法调用转发给内部对象中对应的方法之前执行适当的同步操作。这样的做法正是JDK Collection的做法,很经典,可以专门看一下设计模式。
第2种做法就是设计一个不支持同步的类,但是在子类中只包含一些被同步的方法,这个做法最简单。但是扩展性不好。
要选择第一种做法,在类的内部进行同步的理由,就是因为它将被大量地并发使用,而且我们深入类的内部进行同步操作,性能上能得到很好提升。例如,实现一个不可变尺寸的散列表,并且它可以独立的对每个散列桶进行同步访问,这是有可能的。这样做比锁住整个散列表以访问单个条目的做法,可以获得很好并发性。
总结防止死锁的一点:不要尝试在同步区域内部去调用外部的方法。并且尽量去限制同步区域内部的工作量,当你在设计一个可变类的时候,请考虑一下它们是否需要自己完成同步操作。你应该了解你的类是否应该考虑线程安全问题。
避免过多的同步
最新推荐文章于 2022-04-08 16:30:02 发布