线程无处不在
即使你的程序没有显式地创建任何线程,框架也可能为你创建了一些线程,这些线程调用的代码必须是线程安全的(thread-safe)。这一点给开发人员的设计和实现赋予了更重要的一份责任,因为开发线程安全的类要比非线程安全的类需要更加仔细,进行更多的分析。
每一个Java应用程序都使用线程。当JVM启动后,它创建一些线程来进行自身的常规管理(垃圾回收,终结处理),以及一个运行main函数的主线程。AWT和Swing用户接口框架创建线程来管理用户接口事件。Timer创建执行延迟的任务线程。组件框架,比如servlet和RMI,会创建线程池,池中线程调用组件方法。
如果你使用这些功能——像大多数程序员一样——你必须熟悉并发和线程安全,因为这些框架创建线程,并在线程中调用你的组件。如果你愿意自欺欺人地认为并发是“可选的”、“高级的”语言特性,这很好。不过事实上,几乎所有的Java应用程序都是多线程的,并且这些框架也不能让你完全避开对应用程序状态访问作适当的协调。
当并发由一个框架引入到一个应用程序中,并不能仅仅让框架的代码知晓并发的存在。因为框架代码本质上是通过回调来使用程序组件,而这些组件才真正用来访问程序状态。与之类似的是,线程安全的需要并不仅仅在于框架调用的组件——只要它处于组件访问过的程序状态段,它就会扩展到所有代码路径。因此,线程安全的需要是具有传递性的。
通过从框架线程中调用应用程序的组件,框架把并发引入了应用程序。组件总是需要访问程序的状态。因此要求在所有代码路径访问状态时,必须是线程安全的。
|
以下描述的这些场景,都会引发非应用程序管理线程调用应用程序代码这种情况。当需要线程安全的时候,可能会以这样的情况作为开始,可是这样做几乎都不能正常结束;反而会影响到整个程序。
定时器。Timer用来调度一些稍后运行的任务,也可以是只运行一次或者周期性运行的任务,这是一种非常方便的机制。引入Timer可以使一个通常简单的顺序程序变复杂,因为TimerTasks运行在由Timer管理的线程中,并不是由应用程序来管理。如果一个TimerTask访问了其他应用程序线程正在访问的数据,那么不仅TimerTask需要线程安全的手段,并且其他那些同时访问这个数据的类也需要相应措施。通常最简单的实现方法
是确保TimerTask访问的对象本身是线程安全的,因此应该将线程安全性封装到共享对象的内部。
Servlets and JavaServer Pages (JSPs) Servlets框架的设计目的是处理Web应用的部署,分发来自远程HTTP客户的请求这些的基础层业务。一个请求到达Server并被分发后,可能通过一个过滤器链到达相应的Servlet或者JSP。每一个Servlet代表应用逻辑的一个组件,在访问量较大的网站中,许多客户可能同时对相同Servlet的服务提出请求。Servlets的规范规定了一个Servlet必须为多个用户同时调用它作好准备。换句话说,Servlets应该是线程安全的。
即使你能够保证一个Servlet一次只被一个线程调用,你在建立一个Web应用程序时可能仍然需要注意线程安全。Servlets通常访问与其他Servlets共享的状态信息,比如程序范围内的对象(那些存储于ServletContext的对象)或者Session范围内的对象(这些保存在每个客户的HttpSession中)。当一个Servlet访问的对象是在Servlets间共享或者请求间共享时,必须对这些对象的访问控制进行适当协调,因为来自不同线程的多个请求可能同时访问它们。Servlet、JSP和Servlet Filter以及那些存储在ServletContext和HttpSession容器中的对象,明显必须是线程安全的。
远程方法调用(Remote Method Invocation) RMI使你能够调用在另外一个JVM上运行的对象的方法。当你使用RMI调用一个远程方法时,这个方法的参数被打包(装配)成一个比特流,并且穿越整个网络到达远程JVM,在那里它会被解包(分解)并传递给远程方法。
当RMI代码调用了你的远程对象时,这个调用发生在哪一个线程?你并不知道,但绝对不是你创建的那个线程——你的对象被RMI管理的一个线程所调用。RMI创建了多少个线程?在许多RMI线程中,同一个对象的同一个方法是不是有可能同时被调用4?
一个远程对象必须去守卫两种线程安全风险:对那些可能会与其他对象共享的状态进行适当调节,应正确地对远程对象本身进行调控(因为相同的对象可能同时被多个线程调用)。比如servlets,RMI对象应该对同时发生的多个调用有所准备,并且必须提供它们自己的线程安全。
Swing和AWT GUI应用程序具有固有的异步特性。用户可能选择一个菜单项,或者在任何时候按下一个按钮,他们希望程序迅速作出响应,即使程序正在做其他事情。Swing和AWT通过创建一个单独的线程来处理用户发起的事件,并更新那些展现给用户的图形界面。
Swing组件,比如JTable,都不是线程安全的。相反,Swing程序通过限制访问事件线程中的GUI组件,实现了它们的线程安全。如果应用程序希望从事件线程之外进行操控,它必须促使操控GUI的代码在事件线程中运行。
当用户进行了一个UI活动,事件线程会调用事件处理器(handler)来执行用户请求的操作。如果处理器需要访问应用程序状态,这里很可能有其他程序在正在访问(比如正在编辑的文件),那么事件的处理器,与其他访问状态的代码必须以线程安全的方式工作。