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

不共享、不可变、同步

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

这一篇不说语言特性和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. 多线程DP

    Matrix Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  2. 【bzoj4889】: [Tjoi2017]不勤劳的图书管理员 分块-BIT

    [bzoj4889]: [Tjoi2017]不勤劳的图书管理员 题目大意:给定一个序列(n<=50000),每个数有一个编码ai(ai<=50000)和权值vi(vi<=100000 ...

  3. python+requests接口自动化测试框架实例详解

    python+requests接口自动化测试框架实例详解   转自https://my.oschina.net/u/3041656/blog/820023 摘要: python + requests实 ...

  4. python3入门之集合set

    之前介绍python的数据结构时,没有介绍set(集合)现在在这里稍微介绍下: set原理 Python 还 包 含 了 一 个 数 据 类 型-- set ( 集 合 ) . 集 合 是 一 个 无 ...

  5. iOS 逆向工程

    HOOK(钩子函数)在OCD动态化语言中使用swizzle method (交换方法来实现) 实际上静态C语言中的函数也是有办法hook的,这也说明绝对的静态语言是不存在的 Mach-O:对于每个操作 ...

  6. css3中的translate,transform,transition的区别

    translate:移动,transform的一个方法               通过 translate() 方法,元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) ...

  7. TX 下常用的查询指令

    查看Jetson TX2 L4T版本 head -n 1 /etc/nv_tegra_release 查看系统版本 cat /etc/lsb-release 查看系统l内核 uname -a 查看内存 ...

  8. Elasticsearch 因拷贝多余的jar到lib库导致无法启动的问题

    因为需要测试,无意中拷贝了一个netty-buffer-4.1.16.Final.jar包放到es的lib目录下,晚上回家启动es的时候发现启动不起来了.检查日志发现如下错误. 其中有一句关键语句 C ...

  9. PHP将多维数组变成一维数组

    function reduceArray($array) { $return = []; array_walk_recursive($array, function ($x) use (&$r ...

  10. java面向对象概念1

    一.java面向对象学习的三条主线: 1.java类及类的成员:属性.方法.构造器:代码块.内部类 2.面向对象的三大特征:封装性.继承性.多态性.(抽象性) 3.其它关键字:this.super.s ...