编程到接口和同步集合

8
这个问题涉及Java集合——具体来说是Hashtable和Vector——但也可能适用于其他地方。
我在很多地方看到过编写接口的好处,我完全同意。例如,能够编写List接口,而不必考虑底层实现,肯定有助于解耦和测试。对于集合,我可以看出ArrayList和LinkedList在不同情况下都适用,因为它们在内部存储结构、随机访问时间等方面存在差异。然而,这两种实现可以在同一个接口下使用...这很棒。
我无法理解的是某些同步实现(特别是Hashtable和Vector)如何与这些接口相适应。对我来说,它们似乎不符合模型。大多数底层数据结构实现似乎在数据存储方式上存在差异(LinkedList、Array、排序树等),而同步处理的是数据访问时的条件(锁定条件)。让我们看一个返回Map集合的示例:
public Map<String, String> getSomeData();

假设应用程序完全不关心并发性。在这种情况下,我们通过接口操作方法返回的任何实现...每个人都很高兴。世界是稳定的。
但是,如果应用程序现在需要关注并发性呢?我们现在无法忽略底层实现而进行操作 - Hashtable 可以,但必须为其他实现提供服务。让我们考虑3种情况:
1)在使用集合添加/删除时使用同步块等强制同步。然而,在返回同步实现(Hashtable)的情况下,这是否过度了呢?
2)更改方法签名以返回Hashtable。然而,这将紧密绑定到Hashtable实现,因此,编程到接口的优势被抛到了窗外。
3)利用并发包并更改方法签名以返回ConcurrentMap接口的实现。对我来说,这似乎是前进的方向。
基本上,似乎某些同步实现在集合框架中有点不适合,因为在编程到接口时,同步问题几乎迫使人们考虑底层实现。
我完全错过了重点吗?
谢谢。

2
我认为关键在于仅仅在这里或那里添加几个synchronized关键字并不能使您的程序线程安全。即使使用java.util.concurrent集合,您也希望将它们保持为私有实现细节。 - Tom Hawtin - tackline
4个回答

6

1) 是的,这会过度设计
2) 正确,不应该这样做
3) 取决于情况。

问题在于,正如您已经知道的那样,编程接口描述应用程序所做的事情(而不是如何做到这一点,这是实现)

同步从后续实现中删除了(请记住,Vector和Hastable是Java 1.2之前的先前版本,随后出现的ArrayList和HasMap未进行同步,但它们都分别实现了List和Map接口),因为它们会导致由于过度同步而产生性能惩罚。例如,如果您在单个线程中使用向量,则仍然在该单个线程中进行同步。

在多个线程之间共享数据结构是设计应用程序时必须考虑的事情。在那里,您将选择要使用的方法,并选择谁负责保持数据状态清洁。

这就是您在选项1或3之间进行选择的地方。是否需要手动同步?我们应该使用同步接口吗?我们将支持哪个版本等等。

例如,如果您选择1,您还可以在设计中拒绝某些实现(即向量)

数据同步不是“运气”带来的,你必须设计才能正确地发生并且不会引起更多问题。在这个设计过程中,你应该注意选项(实现)和/或底层基础设施。

避免过度同步的最简单方法是使用不可变数据,并且不与其他线程共享数据。

类似于 Martin Fowler 的分布式计算第一定律:

"因此,我们得出了我关于分布式对象设计的第一定律:不要分配你的对象。"

第一条多线程应用定律是什么?难道是“不共享你的数据”吗?

:)

最后提醒:Collections 类提供了一些接口的“同步”版本。

同步列表

同步映射

同步集合


1
你所面临的问题是在多线程环境下,客户端不能简单地使用具有可变共享状态的对象。仅凭集合接口本身无法告诉你如何安全地使用该对象。返回ConcurrentMap可以提供一些额外的信息,但仅适用于特定情况。
通常,您需要在文档(例如javadoc)中单独传达线程安全问题,或者使用自定义注释,如Java Concurrency in Practice所述。返回对象的客户端将必须使用自己的锁定机制或您提供的锁定机制。接口通常与线程安全无关。
如果客户端知道所有实现都来自Concurrent实现,那么这不是问题,但该信息并未由接口本身传达。

0
Java的VectorHashtable早于JDK 5中添加的当前并发包。在编写Vector时,人们认为将其同步化是一个好主意,但随后在企业使用中遇到了性能问题。并发无疑是其中一种情况,在这种情况下,代码接口模块化可能并不总是奏效。

1
这个问题有点更加复杂:1.4版本引入了一个更新的集合框架,包括java.util.Collections.synchronizedMap等类,推荐使用它们代替Hashtable。然后1.5版本引入整个并发包。但是仍然可以在标准库中找到Hashtable,而且可能会一直存在下去...“Java方法”的做法似乎是认为同步是实现细节,需要通过文档而不是API声明来传达对其的要求。 - araqnid
同意。这两个类都是历史悠久的,我相信它们可以追溯到Java 1.0。我确定它们可以追溯到1.2,但在网上找不到古老的API。 - Dean J
@araqnid,“Java方式”的道路上,经过多年的性能、并发等方面的血和疤痕。特别是自从它在服务器端使用后,规模已经倾向于依赖注入、代码接口化、架构宇航员的方式,而不是简单的“new Vector();”。 - Eugene Yokota

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接