保证并发安全性的方式有三:

不共享、不可变、同步

前两种方式相对第三种要简单一些。

这一篇不说语言特性和API提供的相关同步机制,主要记录一下关于共享的一些思考。

共享(shared),可以简单地认为多个线程可以同时访问某个对象。

如果仅仅在单线程内进行访问则不存在同步的问题。

保证数据的单线程访问称为线程封闭(thread confinement)。

线程封闭有三种方式:

·Ad-hoc线程封闭

·栈封闭

·ThreadLocal

Ad-hoc线程封闭

通过程序实现来进行线程封闭,也就是说我们无法利用语言特性将对象封闭到特定的线程上,这一点导致这种方式显得不那么可靠。

举个例子,假设我们保证只有一个线程可以对某个共享的对象进行写入操作,那么这个对象的"读取-修改-写入"(比如自增操作)在任何情况下都不会出现竟态条件。

如果我们为这个对象加上volatile修饰则可以保证该对象的可见性,任何线程都可以读取该对象,但只有一个线程可以对其进行写入。

这样,仅仅通过线程封闭+volatile修饰就适当地保证了其安全性,相比直接使用synchoronized修饰,虽然更适合,但实现起来稍微复杂。

而对于线程封闭方式的选择,这种方式是最不被推荐的。

栈封闭

这个方式理解起来比较简单,封闭在执行线程是局部变量本身固有的特性,封闭在执行线程的栈里,其他线程无法访问是理所当然的。

对于基本类型的局部变量,我们不用考虑任何事情,因为Java语言特性本身就保证了任何方法都无法获得基本类型的引用。

而对于引用类型的局部变量,我们需要稍微注意一些问题来保证其栈封闭。

参考下面的装载方舟的代码,现在我们要保护animals,则需要保证该方法的参数、调用的外来方法、返回值都不会引用到animals:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public int loadTheArk(Collection<Animal> candidates) {
        SortedSet<Animal> animals;
        int numPairs = 0;
        Animal candidate = null;
 
        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的参数candidates,我们将它的元素进行筛选后装载到了方舟中,方法结束后无法通过该参数影响方舟中的动物夫妇。

其次是外来方法,我们使用了"种类性别比较器"对animals进行排序,但它是一个concrete,不会有不确定的行为对animals的状态产生影响。

最后是返回值,显然我们是想报告装载了多少对动物夫妇,返回类型是个基本类型,无法引用animals。

好了,这就是个成功的栈封闭。

ThreadLocal

给人一种亲切感,这几乎是很常见的方式,而且也是最规范的方式。

我们通常用ThreadLocal保证可变的单例变量和全局变量不被多线程共享。

先让我们想想单线程场景中使用Connection对象连接数据库,鉴于Connection对象的初始化开销,整个应用中会维护一个全局的Connection对象。

如果我们想将这个应用改为多线程的,鉴于Connection对象本身不是线程安全的,我们需要对其进行线程封闭,此时我们可以使用ThreadLocal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ConnectionDispenser {
    static String DB_URL = "jdbc:mysql://localhost/mydatabase";
 
    private ThreadLocal<Connection> connectionHolder
            new ThreadLocal<Connection>() {
                public Connection initialValue() {
                    try {
                        return DriverManager.getConnection(DB_URL);
                    catch (SQLException e) {
                        throw new RuntimeException("Unable to acquire Connection, e");
                    }
                };
            };
 
    public Connection getConnection() {
        return connectionHolder.get();
    }
}

不仅是Connection这种场景,如果我们的很多操作频繁地用到某个对象,而我们又需要考虑它的线程封闭又需要考虑它的初始化开销,ThreadLocal几乎是最好的选择。

虽然这看起来有点像一个全局的Map<Thread,T>,事实上也可以这样理解,但其实现并不是这样你懂的。

当然,这种方式很方便,但这并不代表ThreadLocal可以滥用, 比如仅仅是考虑到应用的并发安全性就把全局变量一律变成ThreadLocal。

而这种做法会导致全局变量难以抽象,并降低其可重用性,而且也增加了耦合。

Java - 线程封闭的更多相关文章

  1. Java中ThreadLocal无锁化线程封闭实现原理

    虽然现在可以说很多程序员会用ThreadLocal,但是我相信大多数程序员还不知道ThreadLocal,而使用ThreadLocal的程序员大多只是知道其然而不知其所以然,因此,使用ThreadLo ...

  2. Java并发编程-线程可见性&线程封闭&指令重排序

    一.指令重排序 例子如下: public class Visibility1 { public static boolean ready; public static int number; } pu ...

  3. java并发编程可见性与线程封闭

    可见性 所谓可见性,指的是当一个线程修改了对象的状态后,其他线程能够看到该对象发生的变化.在单线程环境下,向某个变量写入值,然后在后面的操作再读取,在这个过程中该变量的值对该线程来说总是可见.但是,在 ...

  4. Java多线程——线程封闭

    线程封闭:当访问共享的可变数据时,通常需要同步.一种避免同步的方式就是不共享数据.如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭(thread  confinement) 线程封闭技术一 ...

  5. 《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性

    这章的主要内容是:如何共享和发布对象,从而使它们能够安全地由多个线程同时访问. 内存的可见性 确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化. 上面的程序中NoVisibility可能 ...

  6. Java并发编程:线程封闭和ThreadLocal详解

    转载请标明出处: http://blog.csdn.net/forezp/article/details/77620769 本文出自方志朋的博客 什么是线程封闭 当访问共享变量时,往往需要加锁来保证数 ...

  7. Java并发编程(七)线程封闭

    当访问共享的可变数据时,通常需要使用同步.一种避免使用同步的方式就是不共享数据. 如果仅在单线程内访问数据,就不需要同步.这种技术被称为线程封闭(Thread Confinement),它是实现线程安 ...

  8. Java 并发编程(二)对象的公布逸出和线程封闭

    对象的公布与逸出 "公布(Publish)"一个对象是指使对象可以在当前作用域之外的代码中使用.可以通过 公有静态变量.非私有方法.构造方法内隐含引用 三种方式. 假设对象构造完毕 ...

  9. Java并发之线程封闭

    读者们好! 在这篇博客中,我们将探讨线程封闭是什么意思,以及我们如何实现它. 所以,让我们直接开始吧. 1. 线程封闭 大多数的并发问题仅发生在我们想要在线程之间共享可变变量或可变状态时.如果在多个线 ...

随机推荐

  1. 一文读懂加固apk的开发者是怎么想的

    有人说加固会明显拖慢启动速度,同时造成运行卡顿,严重降低用户体验,而且加固是完全可以脱壳的,只需要pc配合进行断点调试就能抓到解密后的dex文件,加固并没有所说的那么安全. 但是为什么还有一大批开发者 ...

  2. [Maven实战-许晓斌]-[第二章]-2.3安装目录分析

    bin boot conf settings.xml非常重要 这个是maven安装包自带的settings.xml 通常我们会放在习惯路径,C:\Users\admin\.m2\下面 即  用户路径\ ...

  3. [转贴]VC编译器版本号_MSC_VER and _MSC_FULL_VER

    Visual Studio version and discrimination macros Abbreviation Product name [Visual Studio version] †1 ...

  4. 多行select中的数据展示和单个删除

    /** 删除多选select中 的某个值,公共方法 只适用于同级节点下只有一个select的情况 v 此按钮,this _id,option中的value的name属性 _name,option中的t ...

  5. 大数据框架:Spark vs Hadoop vs Storm

    大数据时代,TB级甚至PB级数据已经超过单机尺度的数据处理,分布式处理系统应运而生. 知识预热 「专治不明觉厉」之“大数据”: 大数据生态圈及其技术栈: 关于大数据的四大特征(4V) 海量的数据规模( ...

  6. eclipse项目目录展示结构设置

    我因为前后端都搞过, 解除过很多的开发IDE,说真的,很多的项目目录结构都是一级一级分开,然后我可以通过展开等操作来查看文件等资源信息,结果呢?java的开发IDE eclipse默认的项目目录展示简 ...

  7. using声明和using指示

    using声明(using declaration) using namespacename::namespacemember; using声明一次只引入命名空间的一个成员.从效果上看就好像using ...

  8. 封装通用的xhr对象(兼容各个版本)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. 前端cookie、localStorage、sessionStorage缓存技术总结

    转载自:https://www.cnblogs.com/belove8013/p/8134067.html 1.Cookie JavaScript是运行在客户端的脚本,因此一般是不能够设置Sessio ...

  10. epoll中epoll_data_t 中fd和ptr的用法

    https://blog.csdn.net/u011123091/article/details/81867078 Linux高性能服务器P152