`
jokermanager
  • 浏览: 143852 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

线程封闭

阅读更多

线程封闭

新一篇: 线程安全

<script>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>

访 问共享的、可变的数据要求使用同步。一个可以避免同步的方式就是不共享数据。如果数据仅在单线程中被访问,就不需要任何同步。线程封闭(Thread confinement)技术是实现线程安全的最简单的方式之一。当对象封闭在一个线程中时,这种做法会自动成为线程安全的,即使被封闭的对象本身并不是 [CPJ 2.3.2]。

Swing发展了线程封闭技术。Swing的可视化组件和数据 模型对象并不是线程安全的,它们是通过将它们限制到Swing的事件分发线程中,实现线程安全的。为了正确地使用Swing,运行在不同于事件线程 (event thread)的其他线程中的代码不应该访问这些对象(为了简化这些,Swing提供了invokeLater机制,用于在事件线程中安排执行 Runnable实例)。

很多Swing应用中的并发错误都滋生于从其他线程中错误地使用这些被限制的对象。

另一种常见的使用线程 限制的应用程序是应用池化的JDBC(Java Database Connectivity)Connection对象。JDBC规范并没有要求Connection对象是线程安全的9。然而在典型的服务器应用中,线程 总是从池中获得一个Connection对象,并且用它处理一个单一的请求,最后把它归还。每个线程都会同步地处理大多数请求(比如Servlet请求或 者EJB(Enterprise java Bean)调用),而且在Connection对象在被归还前,池不会将它再分配给其他线程,因此,这种连接管理模式隐式地将Connection对象限制在处于请求处理期间的线程中。

正如语言并未提供强迫 变量被锁保护的机制一样,语言也没有办法将对象限制在某一线程中。线程限制是你在程序设计中需要考虑的一个元素,它是在程序的实现中完成的。语言自身以及 核心库提供了某些机制(本地变量和ThreadLocal类)有助于维护线程限制,尽管如此,程序员仍然要自己负责确保线程限制对象不会从它所在的线程中 逸出。

3.3.1   Ad-hoc线程限制

Ad-hoc线 程限制10是指维护线程限制性的任务全部落在实现上的这种情况。因为没有可见性修饰符与本地变量等语言特性协助将对象限制在目标线程上,所以这种方式是非 常容易出错的。事实上,对于像GUI应用中的可视化组件或者数据模型这些线程限制对象,对它们的引用通常是公用域。

如果决定将一个像GUI这样特定的子系统实现为“单线程化”的子系统,通常就要使用线程限制技术。单线程化子系统有时所带来的简便性的好处远远胜过ad-hoc线程限制的易损性11。

线程限制的一种特例是 将它用于volatile变量。只要你确保只通过单一线程写入共享的volatile变量,那么在这些volatile变量上执行“读-改-写”操作就是 安全的。在这种情况下,你就将修改操作限制在单一的线程中,从而阻止了竞争条件。并且,可见性保证volatile变量能够确保其他线程能看到最新的值。

鉴于ad-hoc线程限制固有的易损性,因此应该有节制地使用它。如果可能的话,用一种线程限制的强形式(栈限制或者Thread Local)取代它。

3.3.2  栈限制

栈限制是线程限制的一种特例,在栈限制中,只能通过本地变量才 可以触及对象。正如封装使不变约束更容易被保持,本地变量使对象更容易被限制在线程本地中。本地变量本身就被限制在执行线程中;它们存在于执行线程栈。其 他线程无法访问这个栈。栈限制(也称线程内部或者线程本地用法,但是不要与核心库类的ThreadLocal混淆)与ad-hoc线程限制相比,更易维 护,更健壮。

像清单3.9中loadTheArk方法的numPairs,对于这些基本(primitive)类型的本地变量,你无法尝试去利用栈限制。由于无法获得基本类型的引用,所以语言语义确保了基本本地变量总是线程封闭的。

清单3.9  本地的基本类型和引用类型的变量的线程限制

public int loadTheArk(Collection<Animal> candidates) {

    SortedSet<Animal> animals;

    int numPairs = 0;

    Animal candidate = null;

    // animals被限制在方法中, 不要让它们逸出!

    animals = new TreeSet<Animal>(new SpeciesGenderComparator());

    animals.addAll(candidates);

    for (Animal a : animals) {

        if (candidate == null || !candidate.isPotentialMate(a))

            candidate = a;

        else {

            ark.load(new AnimalPair(candidate, a));

            ++numPairs;

            candidate = null;

        }

    }

    return numPairs;

}

维护对象引用的栈限制,需要程序员多一些付出,来确保引用的对 象没有逸出。在loadTheArk中,我们实例化一个TreeSet对象animals,并且保存了一个到animals中一个元素的引用。此时只有一 个引用指向集合animals,因此它被限制在保存本地变量的执行线程中。但是,倘若我们发布了到集合animals(或者其他任何内部数据)的引用,那 么将会破坏限制性,也导致了animals对象的逸出。

在线程内部上下文使用非线程安全的对象仍然可以保证线程安全性。不过请小心:无论是对象被限制于执行线程的设计需求,还是对“被限制的对象并非线程安全”这一情况

的明确知晓,都只是存在于一线开发人员编码的那一刻。如果线程内部用法的设定没有清楚地文档化,那么后期维护人员会错误放任对象的逸出。

3.3.3  ThreadLocal

一种维护线程限制的更加规范的方式是使用ThreadLocal,它允许你将每个线程与持有数值的对象关联在一起。ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝。所以get总是返回由当前执行线程通过set设置的最新值。

线程本地(Thread Local)变量通常用于防止在基于可变的单体(Singleton)或全局变量的设计中,出现(不正确的)共享。比如说,一个单线程化的应用程序可能会 维护一个全局的数据库连接,这个Connection在启动时就已经被初始化了。这样就可以避免为每个方法都传递一个Connection。因为JDBC 规范并未要求Connection自身一定是线程安全的,因此,如果没有额外的协调时,使用全局变量的多线程应用程序同样不是线程安全的。通过利用 ThreadLocal存储JDBC连接,如同清单3.10的ConnectionHolder,每个线程都会拥有属于自己的Connection。

清单3.10  使用Thread Local确保线程封闭性

private static ThreadLocal<Connection> connectionHolder

    = new ThreadLocal<Connection>() {

        public Connection initialValue() {

            return DriverManager.getConnection(DB_URL);

        }

    };

public static Connection getConnection() {

    return connectionHolder.get();

}

这项技术还用于下面的情况:一个频繁执行的操作既需要像 buffer这样的临时对象,同时还需要避免每次都重分配(realloate)该临时对象。举例来说,在Java 5.0以前,Integer.toString()方法使用ThreadLocal存储一个12-byte的缓冲区来格式化结果,而不是使用共享的静态缓 冲区(这需要用到锁)或者在每次调用前都分配一个新的缓冲12。

线程首次调用ThreadLocal.get方法时,会请求initialValue 提供一个初始值。概念上,你可以将Thread Local<T>看作map< Thread,T>它存储了与线程相关的值,不

过事实上它并非这样实现的。与线程相关的值存储在线程对象自身中,线程终止后,这些值会被垃圾回收。

假设你正在将一个单线程的应用迁移到多线程环境中,你可以将共享的全局变量都转换为ThreadLocal类型,这样可以确保线程安全。前提是全局共享(shared globals)的语义允许这样。如果将应用级的缓存变成一堆线程本地缓冲,它将毫无价值。

实现一个应用程序框架会广泛地使用ThreadLocal。举 例来说,在EJB调用期间,J2EE容器把一个事务上下文与一个可执行线程关联起来。下面是一种简单的实现,它利用静态ThreadLocal持有事务上 下文:当框架代码需要获知当前正在运行的是哪个事务时,只要从ThreadLocal中获得事务的上下文即可。这样很方便,因为它降低了为每个方法传递执 行上下文信息的需要,不过却增加了任何使用该机制的代码与框架间的耦合。

ThreadLocal很容易被滥用:比如将它们所封闭的属性作为使用全局变量的许可证,或者是创建一种将方法的参数“隐藏”起来的方法。如同全局变量,线程本地变量会降低重用性,引入隐晦的类间的耦合。因此应该谨慎地使用它们。

分享到:
评论

相关推荐

    java多线程中线程封闭详解

    Java多线程中,线程封闭是一种重要的并发控制策略,其核心思想是限制共享数据的访问,从而避免因多线程并发导致的数据不一致问题。本文将深入探讨线程封闭的概念、实现方式及其应用场景。 线程封闭的概念在于,确保...

    java线程封闭之栈封闭和ThreadLocal

    Java 线程封闭之栈封闭和 ThreadLocal Java 线程封闭是多线程编程中的一种常见技术,将资源封闭在每个线程中,以保证线程安全。栈封闭和 ThreadLocal 是 Java 线程封闭中两种常见的实现方式。 栈封闭 栈封闭是将...

    Java并发中线程封闭知识点详解

    Java并发编程中,线程封闭是一种重要的策略,用于解决多线程环境下的数据安全性问题。线程封闭的基本思想是确保对象或变量仅在一个线程内使用,从而避免并发访问带来的潜在冲突。本文将深入探讨线程封闭的三种主要...

    Java中的线程同步与ThreadLocal无锁化线程封闭实现

    ThreadLocal,全称为`java.lang.ThreadLocal`,是Java提供的一种线程局部变量,它的每个线程都有自己的副本,线程间互不影响,从而实现无锁化线程封闭。ThreadLocal不是用来解决线程同步问题的,而是用来避免线程间...

    java多线程安全性基础介绍.pptx

    线程封闭 仅在单线程内访问数据 栈封闭 局部变量 threadLocal 为每个线程保存一个副本 类似于一个以线程id为key的map 不可变对象状态 final 关键共享资源上互斥,变并发为串行即同步 锁 分类 显示锁 ...

    三歪教你学多线程1

    接下来的部分会深入讨论Thread类的API,包括设置线程名、守护线程、优先级、线程生命周期等方法,以及线程安全、对象发布与逸出、解决线程问题的方法,如原子性、可见性、线程封闭、不变性等。还会涉及synchronized...

    java 并发编程实践 001

    java 并发编程实践001 ...3.3 线程封闭 3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 …… 共16个章节

    Java高并发编程,构建并发编程知识体系,提升面试成功率

    本课程将结合大量图示及代码演示,带你掌握多线程并发编程(线程安全,线程调度,线程封闭,同步容器等)与高并发处理思路与手段(扩容,缓存,队列,拆分等),构建完整的并发与高并发知识体系,胜任实际开发中并发...

    高并发和并发编程

    通过合理利用线程安全机制、线程封闭和线程调度,开发者可以构建出能够处理高并发场景的系统,从而提高服务的可扩展性和响应速度。在实际开发中,还需要结合具体业务场景,选择合适的并发模型和设计模式,例如单例...

    Java并发编程实战

    3.3.1 Ad-hoc线程封闭 3.3.2 栈封闭 3.3.3 ThreadLocal类 3.4 不变性 3.4.1 Final域 3.4.2 示例:使用Volatile类型来发布不可变对象 3.5 安全发布 3.5.1 不正确的发布:正确的对象被破坏 3.5.2 不可变对象与...

    Java 并发编程实战

    3.3.1 Ad-hoc线程封闭 3.3.2 栈封闭 3.3.3 ThreadLocal类 3.4 不变性 3.4.1 Final域 3.4.2 示例:使用Volatile类型来发布不可变对象 3.5 安全发布 3.5.1 不正确的发布:正确的对象被破坏 3.5.2 不可变对象与...

    Java并发编程part2

    3.3 线程封闭 3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 第5章 构建块 5.1 同步容器 5.2 发容器 5.3...

    Java并发编程实践part1

    3.3 线程封闭 3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 第5章 构建块 5.1 同步容器 5.2 发容器 5.3...

    Java并发学习.ppt

    - **线程封闭**:如Swing的事件分发线程,限制对象在特定线程中使用,保证线程安全。 - **栈限制**:局部变量由于存储在栈中,通常不会被多线程共享。 - **ThreadLocal**:每个线程有自己的副本,避免线程间的...

    Java并发编程学习笔记

    - 线程封闭(Thread Confinement):一种确保线程安全的策略,通常通过ThreadLocal等机制实现。 - 不可变对象(Immutable Objects):一旦创建就不能被修改的对象,天然线程安全。 学习Java并发编程,重点在于理解...

    Java并发编程(学习笔记).xmind

    线程封闭:线程封闭的对象只能由一个线程拥有并修改 Ad-hoc线程封闭 栈封闭 ThreadLocal类 只读共享:不变对象一定是线程安全的 尽量将域声明为final类型,除非它们必须是可变的 分类 ...

    JAVA并发编程实践_中文版(1-16章全)_1/4

    3.3 线程封闭 3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 第5章 构建块 5.1 同步容器 5.2 发容器 5.3 ...

Global site tag (gtag.js) - Google Analytics