线程封闭&栈封闭

34 篇文章 2 订阅

一 概述

当访问共享数据的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭(Thread Confinement),它是实现线程安全的最简单的方式之一。当某个对象封闭在一个线程之中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。

在Swing中大量的使用线程封闭技术。Swing的可视化组件和数据模型对象都不是线程安全的,Swing通常将它们封闭到Swing的事件分发线程中来实现线程安全性。要想正确地使用Swing,那么在除了事件线程之外的其他线程中就不能访问这些对象(为了进一步简化对Swing的使用,Swing还提供了invokerLater机制,用于将一个Runnable实例调度到事件线程中执行)。Swing应用程序的许多并发错误都是由于错误地在另一个线程中使用了这些封闭的对象。

线程封闭技术的另一种常见应用是JDBC(Java DataBase Connectivity)的Connection对象。JDBC规范并不是要求Connection对象必须是线程安全的。在典型的服务器应用程序中,线程从线程池中获取一个Connection对象,并且用该对象来处理请求,使用完后再将对象返还给连接池。由于大多数请求(例如Servlet请求或者EJB调用等)都是由单个线程采用同步的方式来处理,并且再Connection对象返回之前,连接池不会再将它分配给其他线程,因此着这中线程管理模式在处理请求时隐含地将Connection对象封闭在线程中。

在Java语言中并没有强制规定某个变量必须由锁来保护,同样在Java语言中也无法强制将对象封闭在某个线程中。线程封闭是在程序设计中的一个考虑因素,必须在程序中实现。Java语言及其核心库提供了一些机制来帮助维持线程的封闭性,例如局部变量和ThreadLocal类,但即使如此,程序员任然需要负责确保封闭咋线程中的对象不会从线程中逸出。

二 Ad-hoc线程封闭

Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。Ad-hoc线程封闭式非常脆弱的,因为没有任何一个语言特性,例如可见性修饰符或局部变量,能将对象封闭到目标线程上。事实上,对线程封闭对象(例如,GUI应用程序中的可视化组件或数据模型等)的引用通常保存在共有变量中。

当决定使用线程封闭技术时,通常是因为要将某个特定的子系统实现为一个单线程子系统。在某种情况下,单线程子系统提供的简便性要胜过Ad-hoc线程封闭技术的脆弱性。

在volatile变量上存在一种特殊的线程封闭。只要你能确保只有单个线程对共享的volatile变量执行写入操作,那么就可以安全地在这些共享的volatile变量上执行"读取-修改-写入"的操作。在这中情况下,相当于将修改操作封闭在单个线程中以防止发生竞态条件,并且volatile变量的可见性保证还确保了其他线程能看到最新的值。

应用程序服务器提供的连接池是线程安全的,连接池通常会由多个线程同时访问,因此非线程安全的连接池是毫无意义的。

使用单线程子系统的另一个原因是为了避免死锁,这也是大多数GUI框架都是单线程的原因。

由于Ad-hoc线程封闭技术的脆弱性,因此在程序中尽量少用它,在可能的情况下,应该使用更强的线程封闭技术(如,栈封闭或ThreadLocal类)

三 栈封闭

栈封闭式线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。正如封装能使得代码更容易维持不变性条件一样,同步变量也能使对象更易于封闭在线程中。局部变量的固有属性之一就是封闭在执行线程中。它们为执行线程的栈中,其他线程无法访问这个栈。栈封闭(也被称为线程内部使用或者线程局部使用,不要与核心类库中的ThreadLocal混淆),它比Ad-hoc线程封闭更易维护,也更加健壮。

public class StackClose {

    public int loadTheArk(Collection<com.TWO.Boy> canditates) {
        SortedSet<com.TWO.Boy> boys;
        int numPairs = 0;
        com.TWO.Boy canditate = null;

        //boys被封闭在方法中,不要使它们逃逸
        boys = new TreeSet<com.TWO.Boy>();
        boys.addAll(canditates);
        for(com.TWO.Boy boy : boys){
            if(canditate == null) {
                canditate = boy;
            }else {
                ++numPairs;
            }
        }
        return numPairs;
    }
}

对于基本的局部变量,如loadTheArk方法中的numPairs,无论如何都不会破坏栈封闭性。由于任何方法都无法获得对基本类型的引用,因此Java语言的这种语义就确保了基本类型的局部变量始终封闭在线程中。

在维持对象引用的栈封闭性时,程序员需要多做一些工作以确保被引用的对象不会逸出。在loadTheArk中实例化一个TreeSet对象,并将指向该对象的一个引用保存到boys中。此时,只有一个引用指向集合boys,这个引用被封闭在局部变量中,因此也被封闭在执行线程中。然而,如果发布了对集合boys(或者该对象中的任何内部数据)的引用。那么封闭性将被破坏,并导致对象boys的逸出。

如果在线程内部(Within-Thread)上下文中使用非线程安全的对象,那么该对象仍是线程安全的,然而,要小心的是,只有编码人员才知道哪些对象需要被封闭到执行线程中,以及被封闭的对象是否是线程安全的。如果没有明确地说明这些需求,那么后续的维护人员很容易错误地使对象逸出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值