Java Concurrent 线程封闭

作者: 邹志全 | 来源:发表于2019-07-15 22:24 被阅读0次

    前言

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

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

    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(具体的变量集合)。

    源码剖析

    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
    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、建立对象状态的并发访问策略。

    相关文章

      网友评论

        本文标题:Java Concurrent 线程封闭

        本文链接:https://www.haomeiwen.com/subject/dgihkctx.html