前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java Concurrent 线程封闭

Java Concurrent 线程封闭

作者头像
邹志全
发布2019-07-31 10:22:00
2960
发布2019-07-31 10:22:00
举报
文章被收录于专栏:EffectiveCodingEffectiveCoding

前言

并发能够帮助我们完成资源的高效利用,业务逻辑的解耦。但是同时并发也带来了一些问题,比如说数据的并发安全问题。很多情况下,我们是希望能够并行的做一些事情,但是资源最好不共享。

就现状而言,差不多有以下几种方式:

1、Ad-hoc 线程封闭 2、ThreadLocal 3、栈封闭

Ad-hoc 线程封闭

Ad-hoc 封闭,靠意念封闭。首先你需要经历过n多线程并发问题,然后对于自己及别人的当前及今后的代码了如指掌,能够完全预料到将来的发展。然后就有可能实现一个完全没有问题的ad-hoc封闭了,说的真,如果有这个能力还是干点别的吧,别封闭了。这个种方式完全依赖于实现者的控制,但是实际开发中基本不可用的一种方式。

ThreadLocal

可以理解为线程内变量的集中管理,线程之间这些变量不共享。

实现原理

ThreadLocal 里维持了一个map,这个map以当前线程的threadlocal为id存放了关于这个线程的变量副本。在这个场景下很容易误解为ThreadLocal是为了解决共享问题而产生的一个安全并发访问的结构,其实不是这样的,它为每个线程保存一份仅自己可见的变量,ThreadLocal解决的核心问题是线程内部大量传递的参数,也就是说在当前线程所使用资源不共享的情况下(避免了共享带来的线程安全问题),减少线程内部参数传递的数量,使用ThreadLocal来维持或者管理线程内的变量。

具体来说,ThreadLocal 中存在一个叫做ThreadLocalMap的结构,数据就在这个结构中。然后ThreadLocalMap实际上是一个Entry 数组,每个Entry 维持一个弱引用的key(真正的key是Thread的ThreadLocal对象)和一个强引用的value(具体的变量集合)。

源码剖析
代码语言:javascript
复制
static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {

        /** The value associated with this ThreadLocal. */

        Object value;

        Entry(ThreadLocal<?> k, Object v) {

            super(k); // key 值为弱引用

            value = v;

        }

    }

这里弱引用的意义是保证不会内存泄漏不会发生。因为如果是强引用会出现这样的情况:当线程已经完成任务没有对应的调用了,但在这个map中仍然持有对于key的强引用,导致GC时无法对于key及value完成回收,导致内存泄漏,而当为弱引用时,在GC时会被对应的回收,那么key为null时,对应的value也是会被回收的。这里相当于完成一次依赖性的转移,GC回收的依据跟map中的key没有关系,完全依赖于外部的ThreadLocal引用。

虚线箭头代表弱引用,实线箭头代表强引用。除了弱引用策略外,set函数也会定期的清理无用的Entry。

image.png

代码语言:javascript
复制
public void set(T value) {

    Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t);

    if (map != null)

        map.set(this, value); // 注意一下这一行

    else

        createMap(t, value);

}

至于为什么使用ThreadLocal 作为key,怕是因为直接用线程id来作为ThreadLocalMap的key,无法区分放入ThreadLocalMap中的多个value。而使用ThreadLocal作为key就可以做到这一点,由于每一个ThreadLocal对象都可以由threadLocalHashCode属性唯一区分或者说每一个ThreadLocal对象都可以由这个对象的名字唯一区分(下面的例子),所以可以用不同的ThreadLocal作为key,区分不同的value,方便存取。

然后使用上,每次get前需要先set,如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

栈封闭

JVM 中有这么几种内存结构:直接内存(NIO 能够直接操作一部分直接内存)、堆内存、栈内存。其中栈内存是线程私有的,而对内存是线程所共享的。而我们这里利用的就是栈信息是线程私有这一点去实现的。我们都很清楚对象是存放在堆上的,那么如何控制呢?我们知道引用及基础类型是存放于栈帧中的,既然对象不安全,而引用安全,那么我只需要这样就可以了,我们在方法内部创建一个对象,只能局部变量访问,只要不逸出,就能保证这个对象仅有创建它的线程访问。

image.png

如何设计线程安全的类,这一点本来想单独写一篇的,但是发现已经存在总结的非常棒的了。 并发编程实战中说的挺好的: 1、首先找出构成这个类的所有变量(如果是对象,依次向下找) 2、找出约束状态变量的不可变条件。 3、建立对象状态的并发访问策略。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.07.15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Ad-hoc 线程封闭
  • ThreadLocal
    • 实现原理
      • 源码剖析
      • 栈封闭
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档