算是看见它了。下面我哪怕盖面试问答的样式上我们的——大家可以尝试联合来开一下祥和之Crash日志记录。

   

图片 1

前言

ThreadLocal重重同桌都作不知情是啊事物,可以就此来干嘛。但面试时却同时经常问到,所以这次我跟豪门齐声上ThreadLocal这个类。

脚我虽因为面试问答的形式学习我们的——ThreadLocal仿佛(源码分析基于JDK8)

今天以微信公众号上来看同样首文章,做一下简化整理,大家可以尝试同来开一下和谐之Crash日志记录

      今天,你是不是遭遇见其了?如果是,请代表我报告她:我十分欣赏您!我想,再也不会把您下手丢了!                                         ——题记

     
清晨,当室外第一详实阳光掠进自己的房间,暖暖的微光将自我提醒,我懂,是时刻打床了,因为我要是失去变现它。想到这里,我当下欣喜若狂,有点着急,又生出接触小窃喜,仿佛是一个忠于的少年。

     
一抵达公园,我就算快速以晨跑的人流里找她的踪迹,终于看见她了。自己怀念的内心终于放下了,满心欢喜,即使本人相信其早晚在此地!

图片 2

     
初次遇见其的时,也是于这样一个美好的清早,公园里所在鸟语花香,许多总人口且以尽情挥洒在汗珠,享受在清晨异常之率先详尽暖阳和含花草清香的轻风。而以大的人流里,我平双眼就是见了她——那一刻,世间一切还变成虚无,我才看见她迈出着轻盈的步子,悠然的悠悠跑在,白净的脸孔上带来在走中的红晕,就如及了自然之红妆;只看见它若有若无的嘴角边的微笑,我料想:肯定是随即方圆的满愉悦了它,又或是近年遇上了戏谑之转业,但其早晚是独爱笑的女孩。她底乐仿佛含魔力,让自身的心中也仍其载了快乐,我禁不住的和达到了她底步伐,开始了晨跑,哦不,是事后的长跑!这整个,只坐我中见了她。

图片 3

     
为了赶上她,我居然真的每天都交花园去早跑了。双重出乎意料之转业,我好像变了一个人数,每天还焕发,时常会想起她嘴巴角边若有若无的微笑,然后心情十分是快,连工作遭到的闷都掉了。共事见了,总好打笑我说:这家伙一定是于谈恋爱了!我乐着说非是。却于产一致秒,喃喃自语:这毕竟恋爱么?

     
这怎么能够说是恋爱也?毕竟,我尚未达到前方失去和她于过招呼,甚至是咱们尚无互相双目直视,她必然还免明自己之在吧。同时,我未是独轻浮的食指,我弗信赖一见钟情,我本着它们底其他一切还未知。
可是心底的一个声响告诉我:我明明是动了心!
即时一阵子,我还开始小不可知满足于对其的问询仅此而已,很期待跟她相识。

图片 4

     
于是,在一个非同一般的清晨,一庙人为的浪漫邂逅在苑表演了。我们好不容易互相认识了,为之,我心里激动了长期。也许是机缘,一切进步得很当然,我跟其总能心有灵犀,话很合拍,成为了非常好的情人。我开始上了她底活着,我发现其非常欢喜看书籍,各门各类的开都扣留,尤其喜欢旅游专栏,其到底说:“书中打出黄金屋”,读书会吃你被见更好之友善,让您又了解享受生活,透过书籍可以视更常见的天地!原,她随身那幽静优雅的威仪是书培养的,我醒,对它坚持阅读好是观赏,希望为会望其一样养成读书之惯,陪它圈还广泛的世界。

     
自家曾经懂得其是独见面拿不折不扣付诸行动的人。是的,她很热衷旅游。论她说:这一生,希望能尽自己所能,走遍世界。她让自身大吃一惊,虽然自己内心会闪了漫游的想法,但为就限于偶尔去周边旅游,却也十分少付诸行动。遇见它,让我本着出境游充满了再次显眼的仰慕,也不时为她底伴能真正走去外边的世界看。嗯,外面的社会风气真坏精彩!

     

图片 5

     
了解它们越是多,我就算再欣赏和她相处。喜欢同她一起协调动手DIY属于自己的盆栽;喜欢与它们以并享用音乐;喜欢和它共就此好的心情去押世界;喜欢自己制定小目标,然后鼓励自己姣好;喜欢跟它们一同就将来,不念过去,过好马上……看到其,让我想起了同一句话:生活不断眼前的苟且,还有诗与天涯。我异常欣赏它,喜欢它的上上下下,恨不得成为她!

       
可是,她无比美好了,我连望尘莫及。我奋力为它们迈进,我开坚持早跑、读书、旅游、动手DIY盆栽、享受音乐……不过,我连连三分钟热度、各种拖延症、懒癌晚期、没斗志……甚至,我偶尔后会像小女生一样追剧、打游戏!是的,我吃它们失望了。但是,她总未曾放弃自我,一次次底牵连已我之手,试图向自家凑。可自己还是将她弄丢了,我知,有时候它仍会暗地里回到看自己,却再也不会出现在自家前,亲密的带入起自家之手了!

     

图片 6

     
她底离让自身叫打击,我开重复做着一个梦幻,梦里自己紧紧拉在她底手,请她转倒!梦里它们或同如初见般美好,但它却有点带疲劳,轻轻的告知自己:“实际上,我不怕是前景的汝啊!可是,如果你不再要我,那我说不定会见烟消云散了,又或者会成为未来之她还是他,却不再是若了!”我惊醒,很是动摇。

       
自此,我重新为显现不至其的身影了,但自身懂,而自己努力,总有一天,她会见回到自己身边的,又或成为又好之本身!

     
其实生活蒙,每个人的身边还发出这般一个它们,只要您认真留意,努力追寻,总会发现它底踪影的。希望你们永远在一起!愿意每个人且能够生存成温馨直接惦念要变成的规范,不留遗憾!

      今天,你是否中见其了?

   

(注:实则写就首随笔,是为起一致上,某才布置而起了存消极怠工的场景,突发奇想的思念掌握:与和谐和个名字的总人口了得怎么样?龙晓还有哪位会像我如此无聊啊?!于是百度搜索,在微博及发现了一个与自家同名的女孩,看了其底存日志……不算是偷窥吧,哈哈!所以就生了当下篇随笔,竟发现有点像情书。共勉,不喜欢不喷哈!)

问答内容

付出iOS应用,解决Crash问题一直是一个难题。Crash分为两种,一栽是出于EXC_BAD_ACCESS引的,原因是访问了不属于按照进程的内存地址,有或是看已被放走的内存;另一样种是免让捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而倒。其实对于无捕获的Objective-C异常,我们是发出点子将它记录下来的,如果日志记录得当,能够化解绝大部分倒的问题。

1.

问:ThreadLocal问询吗?您能够被我说说他的主要用途吗?

答:

  • 从JAVA官方对ThreadLocal类似的证明定义(定义在演示代码中):ThreadLocal类用来提供线程内部的一部分变量。这种变量在多线程环境下访(通过getset艺术访问)时能够保证各个线程的变量相对独立于其它线程内的变量。ThreadLocal实例通常来说都是private static品种的,用于关联线程和线程上下文。

  • 咱们得识破ThreadLocal的打算是:ThreadLocal的用意是供线程内之一对变量,不同的线程之间未会见彼此干扰,这种变量在线程的生命周期内从作用,减少和一个线程内多只函数或机件之间有些官变量的传递的复杂度。

  • 上述可以概述为:ThreadLocal供线程内部的片变量,在本线程内随时随地可取,隔离其他线程。

以身作则代码:

/**
 * 该类提供了线程局部 (thread-local) 变量。 这些变量不同于它们的普通对应物,
 * 因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量
 * 它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段
 * 它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
 *
 * 例如,以下类生成对每个线程唯一的局部标识符。
 * 
 * 线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,
 * 在后续调用中不会更改。
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // 原子性整数,包含下一个分配的线程Thread ID 
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // 每一个线程对应的Thread ID
 *     private static final ThreadLocal<Integer> threadId =
 *         new ThreadLocal<Integer>() {
 *             @Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // 返回当前线程对应的唯一Thread ID, 必要时会进行分配
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * 每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的
 * 在线程消失之后,其线程局部实例的所有副本都会被垃圾回收,(除非存在对这些副本的其他引用)。
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
·····
   /**
     * 自定义哈希码(仅在ThreadLocalMaps中有用)
     * 可用于降低hash冲突
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * 生成下一个哈希码hashCode. 生成操作是原子性的. 从0开始
     * 
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();


    /**
     * 表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量 
     */
    private static final int HASH_INCREMENT = 0x61c88647;


    /**
     * 返回下一个哈希码hashCode
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
·····

}
  • 其中nextHashCode()方式就是是一个原子类不鸣金收兵地去加上0x61c88647,这是一个分外特别之再三,叫斐波那契散列(Fibonacci
    Hashing),斐波那么契又生出一个称呼被黄金分割,也就是说将之累作为哈希值的增量将见面使哈希表的遍布更为均匀。

一. 系统Crash

2.

问:ThreadLocal落实原理是啊,它是哪就有变量不同的线程之间无会见相互干扰的?

答:

  • 一般性,如果我莫错过看源代码的话,我猜ThreadLocal大凡这样子设计的:每个ThreadLocal好像都创一个Map,然后用线程的ID
    threadID作为Mapkey,要存储的片段变量作为Mapvalue,这样即便能达各个线程的价值隔离的成效。这是无与伦比简单易行的设计方法,JDK最初期的ThreadLocal哪怕是这么设计的。

  • 只是,JDK后面优化了设计方案,现时JDK8
    ThreadLocal的筹划是:每个Thread护卫一个ThreadLocalMap哈希表,这个哈希表的keyThreadLocal实例本身,value才是真正使存储的价Object

  • 夫规划与我们一样开始说的计划刚好相反,这样设计来如下几接触优势:

    1)
    这样设计下每个Map存储的Entry数量就是会见变多少,因为前面的囤数量由Thread的数额控制,现在凡由ThreadLocal的多寡控制。

    2)
    Thread销毁后,对应之ThreadLocalMap呢会见跟着销毁,能压缩内存的动。

ThreadLocal引用关系图- 图片来自于《简书 –
对ThreadLocal实现原理的少数琢磨》

上述解释根本参考自:ThreadLocal和synchronized的区别?

对此网Crash而滋生的次非常退出,可以由此UncaughtExceptionHandler机制捕获;也就是说在次中catch以外的情,被系统自带的错误处理而抓获。我们设做的便是因此从定义的函数替代该ExceptionHandler即可。

3.

咨询:您能够说说ThreadLocal常用操作的平底实现原理也?如存储set(T value),获取get(),删除remove()等操作。

答:

  • 调用get()操作获取ThreadLocal吃对应当前线程存储的价经常,进行了之类操作:

    1 )
    获取当前线程Thread目标,进而赢得之线程对象被维护的ThreadLocalMap对象。

    2 ) 判断时的ThreadLocalMap是否留存:

  • 若存在,则盖当下底ThreadLocal
    key,调用ThreadLocalMap中的getEntry办法赢得相应之囤实体
    e。找到呼应的囤积实体 e,获取存储实体 e 对应之
    value价,即为咱想只要之当前线程对许是ThreadLocal的价值,返回结果值。

  • 若果无存在,则印证这个线程没有保护的ThreadLocalMap对象,调用setInitialValue方法进行初始化。返回setInitialValue初始化的值。

  • setInitialValue办法的操作如下:

    1 ) 调用initialValue得初始化的价。

    2 )
    获取当前线程Thread靶,进而获得之线程对象吃保护的ThreadLocalMap对象。

    3 ) 判断时底ThreadLocalMap是否留存:

  • 假使存在,则调用map.set设置是实体entry

  • 要是非有,则调用createMap进行ThreadLocalMap靶的初始化,并以是实体entry作为第一单价值存放到ThreadLocalMap中。

PS:关于ThreadLocalMap对应的相关操作,放在下一个题目详细说明。

以身作则代码:

    /**
     * 返回当前线程对应的ThreadLocal的初始值
     * 此方法的第一次调用发生在,当线程通过{@link #get}方法访问此线程的ThreadLocal值时
     * 除非线程先调用了 {@link #set}方法,在这种情况下,
     * {@code initialValue} 才不会被这个线程调用。
     * 通常情况下,每个线程最多调用一次这个方法,
     * 但也可能再次调用,发生在调用{@link #remove}方法后,
     * 紧接着调用{@link #get}方法。
     *
     * <p>这个方法仅仅简单的返回null {@code null};
     * 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,
     * 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
     * 通常, 可以通过匿名内部类的方式实现
     *
     * @return 当前ThreadLocal的初始值
     */
    protected T initialValue() {
        return null;
    }

    /**
     * 创建一个ThreadLocal
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

    /**
     * 返回当前线程中保存ThreadLocal的值
     * 如果当前线程没有此ThreadLocal变量,
     * 则它会通过调用{@link #initialValue} 方法进行初始化值
     *
     * @return 返回当前线程对应此ThreadLocal的值
     */
    public T get() {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null) {
            // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 找到对应的存储实体 e 
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取存储实体 e 对应的 value值
                // 即为我们想要的当前线程对应此ThreadLocal的值
                T result = (T)e.value;
                return result;
            }
        }
        // 如果map不存在,则证明此线程没有维护的ThreadLocalMap对象
        // 调用setInitialValue进行初始化
        return setInitialValue();
    }

    /**
     * set的变样实现,用于初始化值initialValue,
     * 用于代替防止用户重写set()方法
     *
     * @return the initial value 初始化后的值
     */
    private T setInitialValue() {
        // 调用initialValue获取初始化的值
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
            createMap(t, value);
        // 返回设置的值value
        return value;
    }

    /**
     * 获取当前线程Thread对应维护的ThreadLocalMap 
     * 
     * @param  t the current thread 当前线程
     * @return the map 对应维护的ThreadLocalMap 
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
  • 调用set(T value)操作设置ThreadLocal中对应当前线程要存储的价经常,进行了之类操作:

    1 )
    获取当前线程Thread对象,进而赢得之线程对象被维护的ThreadLocalMap对象。

    2 ) 判断时的ThreadLocalMap是否存在:

  • 若果存在,则调用map.set设置是实体entry

  • 只要无存,则调用createMap进行ThreadLocalMap目标的初始化,并拿这实体entry作为第一单价存放到ThreadLocalMap中。

示范代码:

    /**
     * 设置当前线程对应的ThreadLocal的值
     * 大多数子类都不需要重写此方法,
     * 只需要重写 {@link #initialValue}方法代替设置当前线程对应的ThreadLocal的值
     *
     * @param value 将要保存在当前线程对应的ThreadLocal的值
     *  
     */
    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
            createMap(t, value);
    }

    /**
     * 为当前线程Thread 创建对应维护的ThreadLocalMap. 
     *
     * @param t the current thread 当前线程
     * @param firstValue 第一个要存放的ThreadLocal变量值
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • 调用remove()操作删除ThreadLocal中对相应前线程已囤积的价值经常,进行了如下操作:

    1 )
    获取当前线程Thread靶,进而获取之线程对象吃保护的ThreadLocalMap对象。

    2 ) 判断当前之ThreadLocalMap是否留存,
    如果存在,则调用map.remove,以当前ThreadLocalkey删除相应的实业entry

  • 以身作则代码:

    /**
     * 删除当前线程中保存的ThreadLocal对应的实体entry
     * 如果此ThreadLocal变量在当前线程中调用 {@linkplain #get read}方法
     * 则会通过调用{@link #initialValue}进行再次初始化,
     * 除非此值value是通过当前线程内置调用 {@linkplain #set set}设置的
     * 这可能会导致在当前线程中多次调用{@code initialValue}方法
     *
     * @since 1.5
     */
     public void remove() {
        // 获取当前线程对象中维护的ThreadLocalMap对象
         ThreadLocalMap m = getMap(Thread.currentThread());
        // 如果此map存在
         if (m != null)
            // 存在则调用map.remove
            // 以当前ThreadLocal为key删除对应的实体entry
             m.remove(this);
     }

二. 处理signal

4.

问:对ThreadLocal的常用操作实际是本着线程Thread中的ThreadLocalMap展开操作,核心是ThreadLocalMap是哈希表,你可知讨论ThreadLocalMap的中底层实现吗?

答:

  • ThreadLocalMap的根实现是一个定制的自定义HashMap哈希表,核心组成元素来:

    1 ) Entry[] table;:底层哈希表 table,
    必要常用展开扩容,底层哈希表 table.length 长度要是2的n次方。

    2 ) int size;:实际存储键值对素个数 entries

    3 ) int threshold;:下同样软扩容时之阈值,阈值 threshold =
    底层哈希表table的尺寸
    len * 2 / 3。当size >= threshold时,遍历table并删除keynull的素,如果去后size >= threshold*3/4时,需要对table展开扩容(详情请查看set(ThreadLocal<?> key, Object value)主意求证)。

  • 其中Entry[] table;哈希表存储的主干元素是EntryEntry包含:

    1 ) ThreadLocal<?> k;:当前囤积的ThreadLocal实例对象

    2 ) Object value;:当前 ThreadLocal 对应储存的值value

  • 得小心的凡,此Entry持续了死亡引用
    WeakReference,所以当采取ThreadLocalMap时,发现key == null,则象征这个key ThreadLocal非在被引用,需要将该于ThreadLocalMap哈希表中移除。(弱引用相关问题说请查看
    问答 5)

演示代码:

    /**
     * ThreadLocalMap 是一个定制的自定义 hashMap 哈希表,只适合用于维护
     * 线程对应ThreadLocal的值. 此类的方法没有在ThreadLocal 类外部暴露,
     * 此类是私有的,允许在 Thread 类中以字段的形式声明 ,     
     * 以助于处理存储量大,生命周期长的使用用途,
     * 此类定制的哈希表实体键值对使用弱引用WeakReferences 作为key, 
     * 但是, 一旦引用不在被使用,
     * 只有当哈希表中的空间被耗尽时,对应不再使用的键值对实体才会确保被 移除回收。
     */
    static class ThreadLocalMap {

        /**
         * 实体entries在此hash map中是继承弱引用 WeakReference, 
         * 使用ThreadLocal 作为 key 键.  请注意,当key为null(i.e. entry.get()
         * == null) 意味着此key不再被引用,此时实体entry 会从哈希表中删除。
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** 当前 ThreadLocal 对应储存的值value. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * 初始容量大小 16 -- 必须是2的n次方.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * 底层哈希表 table, 必要时需要进行扩容.
         * 底层哈希表 table.length 长度必须是2的n次方.
         */
        private Entry[] table;

        /**
         * 实际存储键值对元素个数 entries.
         */
        private int size = 0;

        /**
         * 下一次扩容时的阈值
         */
        private int threshold; // 默认为 0

        /**
         * 设置触发扩容时的阈值 threshold
         * 阈值 threshold = 底层哈希表table的长度 len * 2 / 3
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * 获取该位置i对应的下一个位置index
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * 获取该位置i对应的上一个位置index
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

    }
  • ThreadLocalMap的构造方法是缓加载的,也就是说,只有当线程需要仓储对应之ThreadLocal的价值经常,才初始化创建同糟(仅初始化一糟)。初始化步骤如下:

    1) 初始化底层数组table的启幕容量也 16。

    2)
    获取ThreadLocal中的threadLocalHashCode,通过threadLocalHashCode & (INITIAL_CAPACITY - 1),即ThreadLocal
    的 hash 值 threadLocalHashCode % 哈希表的长短 length
    的艺术计算该实体的囤积位置。

    3) 存储时底实体,key 为 : 当前ThreadLocal value:真正使存储的价

    4)设置当前实际存储元素个数 size 为 1

    5)设置阈值setThreshold(INITIAL_CAPACITY),为初始化容量 16 的
    2/3。

演示代码:

        /**
         * 用于创建一个新的hash map包含 (firstKey, firstValue).
         * ThreadLocalMaps 构造方法是延迟加载的,所以我们只会在至少有一个
         * 实体entry存放时,才初始化创建一次(仅初始化一次)。
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 初始化 table 初始容量为 16
            table = new Entry[INITIAL_CAPACITY];
            // 计算当前entry的存储位置
            // 存储位置计算等价于:
            // ThreadLocal 的 hash 值 threadLocalHashCode  % 哈希表的长度 length
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // 存储当前的实体,key 为 : 当前ThreadLocal  value:真正要存储的值
            table[i] = new Entry(firstKey, firstValue);
            // 设置当前实际存储元素个数 size 为 1
            size = 1;
            // 设置阈值,为初始化容量 16 的 2/3。
            setThreshold(INITIAL_CAPACITY);
        }
  • ThreadLocalget()操作实际是调用ThreadLocalMapgetEntry(ThreadLocal<?> key)办法,此措施迅速适用于博有平存key的实体
    entry,否则,应该调用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)术得到,这样做是以最特别范围地提高直接命中之属性,该措施进行了如下操作:

    1 )
    计算而收获的entry的蕴藏位置,存储位置计算等价于:ThreadLocal
    hashthreadLocalHashCode % 哈希表的长度 length

    2 ) 根据测算的积存位置,获取到相应的实业
    Entry。判断相应实体Entry是不是在 并且 key是不是当:

  • 在对应实体Entry与此同时对应key相等,即同一ThreadLocal,返回对应的实业Entry

  • 莫设有对应实体Entry 或者
    key免对等,则经过调用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方式继续搜寻。

  • getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法操作如下:

    1 )
    获取底层哈希表数组table,循环遍历对承诺设寻找的实业Entry所提到的位置。

    2 ) 获取当前遍历的entry
    key ThreadLocal,比较key是不是相同,一致则回。

    3 ) 如果key不一致 并且 key
    null,则印证引用已经不存在,这是为Entry后续的是WeakReference,这是去世引用带来的坑。调用expungeStaleEntry(int staleSlot)办法去过期的实体Entry(此办法不单独解释,请查看示例代码,有详细注解说明)。

    4 ) key不一致 ,key为无也空,则遍历下一个职务,继续寻找。

    5 ) 遍历完毕,仍然找不交则赶回null

演示代码:

        /**
         * 根据key 获取对应的实体 entry.  此方法快速适用于获取某一存在key的
         * 实体 entry,否则,应该调用getEntryAfterMiss方法获取,这样做是为
         * 了最大限制地提高直接命中的性能
         *
         * @param  key 当前thread local 对象
         * @return the entry 对应key的 实体entry, 如果不存在,则返回null
         */
        private Entry getEntry(ThreadLocal<?> key) {
            // 计算要获取的entry的存储位置
            // 存储位置计算等价于:
            // ThreadLocal 的 hash 值 threadLocalHashCode  % 哈希表
            的长度 length
            int i = key.threadLocalHashCode & (table.length - 1);
            // 获取到对应的实体 Entry 
            Entry e = table[i];
            // 存在对应实体并且对应key相等,即同一ThreadLocal
            if (e != null && e.get() == key)
                // 返回对应的实体Entry 
                return e;
            else
                // 不存在 或 key不一致,则通过调用getEntryAfterMiss继续查找
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * 当根据key找不到对应的实体entry 时,调用此方法。
         * 直接定位到对应的哈希表位置
         *
         * @param  key 当前thread local 对象
         * @param  i 此对象在哈希表 table中的存储位置 index
         * @param  e the entry 实体对象
         * @return the entry 对应key的 实体entry, 如果不存在,则返回null
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
            // 循环遍历当前位置的所有实体entry
            while (e != null) {
                // 获取当前entry 的 key ThreadLocal
                ThreadLocal<?> k = e.get();
               // 比较key是否一致,一致则返回
                if (k == key)
                    return e;
                // 找到对应的entry ,但其key 为 null,则证明引用已经不存在
                // 这是因为Entry继承的是WeakReference,这是弱引用带来的坑
                if (k == null)
                    // 删除过期(stale)的entry
                    expungeStaleEntry(i);
                else
                    // key不一致 ,key也不为空,则遍历下一个位置,继续查找
                    i = nextIndex(i, len);
                // 获取下一个位置的实体 entry
                e = tab[i];
            }
            // 遍历完毕,找不到则返回null
            return null;
        }


        /**
         * 删除对应位置的过期实体,并删除此位置后对应相关联位置key = null的实体
         *
         * @param staleSlot 已知的key = null 的对应的位置索引
         * @return 对应过期实体位置索引的下一个key = null的位置
         * (所有的对应位置都会被检查)
         */
        private int expungeStaleEntry(int staleSlot) {
            // 获取对应的底层哈希表 table
            Entry[] tab = table;
            // 获取哈希表长度
            int len = tab.length;

            // 擦除这个位置上的脏数据
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // 直到我们找到 Entry e = null,才执行rehash操作
            // 就是遍历完该位置的所有关联位置的实体
            Entry e;
            int i;
            // 查找该位置对应所有关联位置的过期实体,进行擦除操作
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // 我们必须一直遍历直到最后
                        // 因为还可能存在多个过期的实体
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
         * 删除所有过期的实体
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
  • ThreadLocalset(T value)操作实际是调用ThreadLocalMapset(ThreadLocal<?> key, Object value)方法,该方法开展了之类操作:

    1 ) 获取相应之底部哈希表table,计算对应threalocal的囤积位置。

    2 ) 循环遍历table针对应当位置的实体,查找对应的threadLocal

    3 )
    获取当前职务的threadLocal,如果key threadLocal一如既往,则证明找到相应之threadLocal,将新值赋值给找到的时实体Entryvalue中,结束。

    4 )
    如果手上位置的key threadLocal不一致,并且key threadLocalnull,则调用replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot)方(此方法不单独解释,请查看示例代码,有详尽注解说明),替换该职位key == null
    的实业为当前一经设置的实体,结束。

    5 )
    如果手上位置的key threadLocal不一致,并且key threadLocal不为null,则创造新的实业,并存放到时岗位
    i
    tab[i] = new Entry(key, value);,实际存储键值对素个数size + 1,由于弱引用带来了之问题,所以只要调用cleanSomeSlots(int i, int n)术清除无用数据(此方不单独解释,请查看示例代码,有详细注解说明),才能够判定现在底size发无发高达阀值threshhold,如果没有假设祛除的数,存储元素个数仍然
    大于 阈值
    则调用rehash方进行扩容(此措施不独立解释,请查看示例代码,有详细注解说明)。

以身作则代码:

        /**
         * 设置对应ThreadLocal的值
         *
         * @param key 当前thread local 对象
         * @param value 要设置的值
         */
        private void set(ThreadLocal<?> key, Object value) {

            // 我们不会像get()方法那样使用快速设置的方式,
            // 因为通常很少使用set()方法去创建新的实体
            // 相对于替换一个已经存在的实体, 在这种情况下,
            // 快速设置方案会经常失败。

            // 获取对应的底层哈希表 table
            Entry[] tab = table;
            // 获取哈希表长度
            int len = tab.length;
            // 计算对应threalocal的存储位置
            int i = key.threadLocalHashCode & (len-1);

            // 循环遍历table对应该位置的实体,查找对应的threadLocal
            for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
                // 获取当前位置的ThreadLocal
                ThreadLocal<?> k = e.get();
                // 如果key threadLocal一致,则证明找到对应的threadLocal
                if (k == key) {
                    // 赋予新值
                    e.value = value;
                    // 结束
                    return;
                }
                // 如果当前位置的key threadLocal为null
                if (k == null) {
                    // 替换该位置key == null 的实体为当前要设置的实体
                    replaceStaleEntry(key, value, i);
                    // 结束
                    return;
                }
            }
            // 当前位置的k != key  && k != null
            // 创建新的实体,并存放至当前位置i
            tab[i] = new Entry(key, value);
            // 实际存储键值对元素个数 + 1
            int sz = ++size;
            // 由于弱引用带来了这个问题,所以先要清除无用数据,才能判断现在的size有没有达到阀值threshhold
            // 如果没有要清除的数据,存储元素个数仍然 大于 阈值 则扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                // 扩容
                rehash();
        }

        /**
         * 当执行set操作时,获取对应的key threadLocal,并替换过期的实体
         * 将这个value值存储在对应key threadLocal的实体中,无论是否已经存在体
         * 对应的key threadLocal
         *
         * 有一个副作用, 此方法会删除该位置下和该位置nextIndex对应的所有过期的实体
         *
         * @param  key 当前thread local 对象
         * @param  value 当前thread local 对象对应存储的值
         * @param  staleSlot 第一次找到此过期的实体对应的位置索引index
         *         .
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            // 获取对应的底层哈希表 table
            Entry[] tab = table;
            // 获取哈希表长度
            int len = tab.length;
            Entry e;

            // 往前找,找到table中第一个过期的实体的下标
            // 清理整个table是为了避免因为垃圾回收带来的连续增长哈希的危险
            // 也就是说,哈希表没有清理干净,当GC到来的时候,后果很严重

            // 记录要清除的位置的起始首位置
            int slotToExpunge = staleSlot;
            // 从该位置开始,往前遍历查找第一个过期的实体的下标
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // 找到key一致的ThreadLocal或找到一个key为 null的
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // 如果我们找到了key,那么我们就需要把它跟新的过期数据交换来保持哈希表的顺序
                // 那么剩下的过期Entry呢,就可以交给expungeStaleEntry方法来擦除掉
                // 将新设置的实体放置在此过期的实体的位置上
                if (k == key) {
                    // 替换,将要设置的值放在此过期的实体中
                    e.value = value;
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // 如果存在,则开始清除之前过期的实体
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    // 在这里开始清除过期数据
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // / 如果我们没有在往后查找中找没有找到过期的实体,
                // 那么slotToExpunge就是第一个过期Entry的下标了
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // 最后key仍没有找到,则将要设置的新实体放置
            // 在原过期的实体对应的位置上。
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // 如果该位置对应的其他关联位置存在过期实体,则清除
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }


        /**
         * 启发式的扫描查找一些过期的实体并清除,
         * 此方法会再添加新实体的时候被调用, 
         * 或者过期的元素被清除时也会被调用.
         * 如果实在没有过期数据,那么这个算法的时间复杂度就是O(log n)
         * 如果有过期数据,那么这个算法的时间复杂度就是O(n)
         * 
         * @param i 一个确定不是过期的实体的位置,从这个位置i开始扫描
         *
         * @param n 扫描控制: 有{@code log2(n)} 单元会被扫描,
         * 除非找到了过期的实体, 在这种情况下
         * 有{@code log2(table.length)-1} 的格外单元会被扫描.
         * 当调用插入时, 这个参数的值是存储实体的个数,
         * 但如果调用 replaceStaleEntry方法, 这个值是哈希表table的长度
         * (注意: 所有的这些都可能或多或少的影响n的权重
         * 但是这个版本简单,快速,而且似乎执行效率还可以)
         *
         * @return true 返回true,如果有任何过期的实体被删除。
         */
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }


        /**
         * 哈希表扩容方法
         * 首先扫描整个哈希表table,删除过期的实体
         * 缩小哈希表table大小 或 扩大哈希表table大小,扩大的容量是加倍.
         */
        private void rehash() {
            // 删除所有过期的实体
            expungeStaleEntries();

            // 使用较低的阈值threshold加倍以避免滞后
            // 存储实体个数 大于等于 阈值的3/4则扩容
            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * 扩容方法,以2倍的大小进行扩容
         * 扩容的思想跟HashMap很相似,都是把容量扩大两倍
         * 不同之处还是因为WeakReference带来的
         */
        private void resize() {
            // 记录旧的哈希表
            Entry[] oldTab = table;
            // 记录旧的哈希表长度
            int oldLen = oldTab.length;
            // 新的哈希表长度为旧的哈希表长度的2倍
            int newLen = oldLen * 2;
            // 创建新的哈希表
            Entry[] newTab = new Entry[newLen];
            int count = 0;
            // 逐一遍历旧的哈希表table的每个实体,重新分配至新的哈希表中
            for (int j = 0; j < oldLen; ++j) {
                // 获取对应位置的实体
                Entry e = oldTab[j];
                // 如果实体不会null
                if (e != null) {
                    // 获取实体对应的ThreadLocal
                    ThreadLocal<?> k = e.get(); 
                    // 如果该ThreadLocal 为 null
                    if (k == null) {
                        // 则对应的值也要清除
                        // 就算是扩容,也不能忘了为擦除过期数据做准备
                        e.value = null; // Help the GC
                    } else {
                        // 如果不是过期实体,则根据新的长度重新计算存储位置
                        int h = k.threadLocalHashCode & (newLen - 1);
                       // 将该实体存储在对应ThreadLocal的最后一个位置
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
            // 重新分配位置完毕,则重新计算阈值Threshold
            setThreshold(newLen);
            // 记录实际存储元素个数
            size = count;
            // 将新的哈希表赋值至底层table
            table = newTab;
        }
  • ThreadLocalremove()操作实际是调用ThreadLocalMapremove(ThreadLocal<?> key)计,该方法开展了之类操作:

    1 ) 获取相应之底部哈希表 table,计算对应threalocal的囤积位置。

    2 ) 循环遍历table对应当位置的实体,查找对应之threadLocal

    3 )
    获取当前位置的threadLocal,如果key threadLocal如出一辙,则证实找到相应之threadLocal,执行删除操作,删除此职务的实业,结束。

演示代码:

        /**
         * 移除对应ThreadLocal的实体
         */
        private void remove(ThreadLocal<?> key) {
            // 获取对应的底层哈希表 table
            Entry[] tab = table;
            // 获取哈希表长度
            int len = tab.length;
            // 计算对应threalocal的存储位置
            int i = key.threadLocalHashCode & (len-1);
            // 循环遍历table对应该位置的实体,查找对应的threadLocal
            for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
                // 如果key threadLocal一致,则证明找到对应的threadLocal
                if (e.get() == key) {
                    // 执行清除操作
                    e.clear();
                    // 清除此位置的实体
                    expungeStaleEntry(i);
                    // 结束
                    return;
                }
            }
        }

运Objective-C的老处理是匪克获signal的,如果只要处理它,我们还要用unix标准的signal机制,注册SIGABRT,
SIGBUS,
SIGSEGV等信号发生时的处理函数
。该函数着我们可输出栈信息,版本信息等任何任何我们所思要之。

5.

问:ThreadLocalMap着的存储实体Entry使用ThreadLocal作为key,但这个Entry是跟着承弱引用WeakReference的,为什么而这样设计,使用了已故引用WeakReference会面导致内存泄露问题也?

答:

  • 首先,回答这个题材之前,我待解释一下什么是青出于蓝引用,什么是死亡引用。

我们以健康情况下,普遍以的是高引用:

A a = new A();

B b = new B();

a = null;b = null;常常,一段时间后,JAVA垃圾回收机制GC会将 a 和 b
对诺所分配的内存空间给回收。

但是考虑这么平等种状况:

C c = new C(b);
b = null;

当 b 被装成null时常,那么是否意味着这一段时间后GC工作得回收 b
所分配的内存空间呢?答案是否认的,因为就 b 被设置成null,但 c
仍然有对 b 的援,而且要大引用,所以GC不见面回收 b
原先所分配的空中,既不能够回收,又无克动用,这即招了 内存泄露。

那怎样处理呢?

得经过c = null;,也可动用弱引用WeakReference w = new WeakReference(b);。因为以了寿终正寝引用WeakReference,GC是可回收
b 原先所分配的空中的。

上述解释根本参考自:本着ThreadLocal实现原理的少数思维

  • 回到ThreadLocal的范畴上,ThreadLocalMap使用ThreadLocal的逝世引用作为key,如果一个ThreadLocal从不外部强引用来引用它,那么网
    GC
    的当儿,这个ThreadLocal毫无疑问会受回收,这样一来,ThreadLocalMap屡遭即使会现出keynullEntry,就没有辙看这些keynullEntryvalue,如果手上线程再缓缓未结束吧,这些keynullEntryvalue即便会见一直在一样久高引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
    永远无法回收,造成内存泄漏。

其实,ThreadLocalMap的设计被一度考虑到这种情形,也丰富了一些防护方法:在ThreadLocalget(),set(),remove()的上都见面破线程ThreadLocalMap里所有keynullvalue

然而这些被动之预防措施并无克担保不见面内存泄漏:

  • 使用staticThreadLocal,延长了ThreadLocal的生命周期,可能造成的内存泄漏(参考ThreadLocal
    内存泄露的实例分析)。

  • 分红使用了ThreadLocal再者不再调用get(),set(),remove()方式,那么尽管会见招致内存泄漏。

打表面上看内存泄漏的源于在以了已故引用。网上的文章大都着重分析ThreadLocal以了寿终正寝引用会促成内存泄漏,但是其他一个题材呢同等值得考虑:为什么用弱引用而非是强引用?

咱们先来探官方文档的说教:

To help deal with very large and long-lived usages, 
the hash table entries use WeakReferences for keys.

以回应好非常与长日子的用途,哈希表使用弱引用的 key

脚我们分开点儿种植状态讨论:

  • key
    使用高引用:引用的ThreadLocal的靶子吃回收了,但是ThreadLocalMap还持有ThreadLocal的赛引用,如果没有手动删除,ThreadLocal勿见面让回收,导致Entry内存泄漏。

  • key
    使用弱引用:引用的ThreadLocal的靶子被回收了,由于ThreadLocalMap持有ThreadLocal的物化引用,即使没手动删除,ThreadLocal也会见叫回收。value在产一样次等ThreadLocalMap调用get(),set(),remove()的时会为扫除。

  • 于少种植状态,我们好窥见:由于ThreadLocalMap的生命周期跟Thread相同长,如果都未曾手动删除相应key,都见面促成内存泄漏,但是使用弱引用得多一致重叠保障:弱引用ThreadLocal勿见面内存泄漏,对应之value当生一样不好ThreadLocalMap调用get(),set(),remove()的时段会受除掉。

因此,ThreadLocal内存泄漏的起源是:由于ThreadLocalMap的生命周期跟Thread如出一辙长,如果没手动删除相应key纵然会见造成内存泄漏,而不是因弱引用。

综合上面的剖析,我们得知晓ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏也?

历次用完ThreadLocal,都调用它的remove()方法,清除数据。

当使用线程池的景象下,没有当即清理ThreadLocal,不仅是内存泄漏的题目,更重的凡唯恐引致事情逻辑出现问题。所以,使用ThreadLocal即跟加锁完而解锁一样,用完就清理。

上述解释根本参考自:深切解析 ThreadLocal
内存泄漏问题

下是部分信号说明:
1) SIGHUP
据信号于用户终端连接(正常或不规则)结束时发出,
通常是于巅峰的支配过程结束时, 通知同一session内之逐条作业,
这时它与控制终端不再涉及。
登录Linux时,系统会分配为登录用户一个终极(Session)。在这个极端运行的所有程序,包括前台进程组和后台进程组,一般都属这
Session。当用户退Linux登录时,前台进程组和后台有对极端输出的过程将会吸收SIGHUP信号。这个信号的默认操作为终止进程,因此前台进
程组和后台有极限输出的长河就会见遭遇只有。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略她,这样尽管退出了Linux登录,
wget也 能继续下载。
此外,对于与极端脱离关系的医护进程,这个信号用于通知她还读取配置文件。

6.

问:ThreadLocalsynchronized的区别?

答:ThreadLocalsynchronized关键字还用来拍卖多线程并发访问变量的题材,只是二者处理问题之角度以及思路不同。

  1. ThreadLocal举凡一个Java类,通过对现阶段线程中的部分变量的操作来缓解不同线程的变量访问的冲突问题。所以,ThreadLocal提供了线程安全之共享对象机制,每个线程都具备其副本。

  2. Java中的synchronized凡一个保留字,它借助JVM的锁机制来贯彻临界区底函数或者变量的看被的原子性。在一齐机制中,通过对象的锁机制保证同一时间只发生一个线程访问变量。此时,被当作“锁机制”的变量时大都个线程共享的。

  3. 手拉手机制(synchronized要字)采用了坐“时间换空间”的方式,提供相同客变量,让不同之线程排队访问。而ThreadLocal采取了“以空间更换时间”的主意,为各级一个线程都提供平等卖变量的副本,从而实现而做客使互不影响。

2) SIGINT
次终止(interrupt)信号,
在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

7.

问:ThreadLocal以现今起什么用场景?

报:总的来说ThreadLocal根本是化解2种类型的问题:

  • 缓解出现问题:使用ThreadLocal代替synchronized来管线程安全。同步机制使了“以日变空间”的章程,而ThreadLocal采用了“以空间更换时间”的道。前者只提供平等份变量,让不同的线程排队访问,而后者也每一个线程都提供了千篇一律份变量,因此可以做客使互不影响。

  • 解决数量存储问题:ThreadLocal否变量在每个线程中都创了一个副本,所以每个线程可以拜自己之中的副本变量,不同线程之间未见面相干扰。如一个Parameter对象的数要以多独模块中使用,如果用参数传递的措施,显然会增多模块之间的耦合性。此时咱们好应用ThreadLocal解决。

下场景:

Spring使用ThreadLocal化解线程安全问题

  • 我们领略在形似情形下,只有无状态的Bean才足以当多线程环境下共享,在Spring遇,绝大部分Bean还足以声明也singleton作用域。就是坐Spring对一些Bean(如RequestContextHolderTransactionSynchronizationManagerLocaleContextHolder相当于)中非线程安全状态下ThreadLocal进行处理,让其也变为线程安全的状态,因为有状态的Bean虽好在差不多线程中共享了。

  • 一般的Web利用细分为表现层、服务层和持久层三只层次,在不同的交汇中修对应之逻辑,下层通过接口向上层开放成效调用。在相似情况下,从收受请求到回响应所经的备程序调用都跟属一个线程ThreadLocal凡釜底抽薪线程安全题材一个坏好的思绪,它经过为每个线程提供一个独立的变量副本解决了变量并发访问的闯问题。在无数景象下,ThreadLocal较直接用synchronized协机制解决线程安全题材重新简短,更有利,且结果程序有所双重胜似之并发性。

以身作则代码:

public abstract class RequestContextHolder  {
····

    private static final boolean jsfPresent =
            ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");

    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");

·····
}

3) SIGQUIT
跟SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制.
进程在为收SIGQUIT退出时见面产生core文件,
在斯意思上好像于一个主次不当信号。

总结

  1. ThreadLocal提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。

  2. ThreadLocal的宏图是:每个Thread保安一个ThreadLocalMap哈希表,这个哈希表的keyThreadLocal实例本身,value才是确实要存储的价值Object

  3. ThreadLocal的常用操作实际是对线程Thread中的ThreadLocalMap拓展操作。

  4. ThreadLocalMap的最底层实现是一个定制的自定义HashMap哈希表,ThreadLocalMap的阈值threshold
    = 底层哈希表table的长度 len * 2 / 3,当实际存储元素个数size
    大于或顶 阈值threshold3/4
    size >= threshold*3/4,则对根哈希表数组table拓展扩容操作。

  5. ThreadLocalMap遭逢之哈希表Entry[] table储存的着力要素是Entry,存储的keyThreadLocal实例对象,valueThreadLocal
    对许储存的价value。需要注意的凡,此Entry继承了身故引用
    WeakReference,所以于采用ThreadLocalMap时,发现key == null,则代表是key ThreadLocal勿以给引用,需要以那个于ThreadLocalMap哈希表中移除。

  6. ThreadLocalMap使用ThreadLocal的凋谢引用作为key,如果一个ThreadLocal尚无外部强引用来引用它,那么网
    GC
    的时段,这个ThreadLocal肯定会为回收。所以,在ThreadLocalget(),set(),remove()的早晚都见面败线程ThreadLocalMap里所有keynullvalue。如果我们不积极调用上述操作,则会招内存泄露。

  7. 为安全地运ThreadLocal,必须要如每次用完锁就解锁一样,在历次用了ThreadLocal继还设调用remove()来清理无用的Entry。这在操作以采取线程池时尤为重要。

  8. ThreadLocalsynchronized的分:同步机制(synchronized第一字)采用了因为“时间更换空间”的主意,提供相同卖变量,让不同之线程排队访问。而ThreadLocal采取了“以空间更换时间”的道,为各级一个线程都提供平等份变量的副本,从而实现以做客使互不影响。

  9. ThreadLocal最主要是化解2种类型的问题:A.
    解决出现问题:使用ThreadLocal代表同步机制解决出现问题。B.
    解决数据存储问题:如一个Parameter目标的数额要以差不多只模块中行使,如果采用参数传递的章程,显然会增加模块之间的耦合性。此时咱们好以ThreadLocal解决。

4) SIGILL
行了私指令. 通常是因可执行文件本身出现谬误, 或者拟实施多少段.
堆栈溢出时为时有发生或发生这信号。

参考文章

深入浅出ThreadLocal
ThreadLocal和synchronized的区别?
深切剖析ThreadLocal
ThreadLocal内部机制
聊一聊Spring中之线程安全性
对ThreadLocal实现原理的少数盘算
深入解析 ThreadLocal
内存泄漏问题
学Spring必学的Java基础知识(6)—-ThreadLocal
ThreadLocal设计模式
ThreadLocal案例解析
Spring单例模式与线程安全ThreadLocal

5) SIGTRAP
由断点指令或外trap指令产生. 由debugger使用。

6) SIGABRT
调用abort函数生成的信号。

7) SIGBUS
黑地址, 包括内存地址对齐(alignment)出错。比如看一个季只字长的平头,
但其地址不是4的翻番。它跟SIGSEGV的区别在于后者是出于针对合法存储地点之伪访问触发的(如访问不属自己储存空间要单独念存储空间)。

8) SIGFPE
当来致命的算术运算错误时发出. 不仅囊括浮点运算错误,
还包溢起同除数为0等任何具有的算术的错。

9) SIGKILL
用来及时终止程序的运行.
本信号不克吃打断、处理与疏忽。如果管理员发现有进程终止不了,可尝试发送这个信号。

10) SIGUSR1
留住用户使用

11) SIGSEGV
计较访问未分配受协调的内存, 或拟为没有写权限的内存地址写数据.

12) SIGUSR2
留下用户使用

13) SIGPIPE
管道破裂。这个信号通常在过程中通信发生,比如采用FIFO(管道)通信的一定量独过程,读管道没打开或者意料之外已就为管道写,写进程会接SIGPIPE信号。此外用Socket通信的个别独过程,写进程在写Socket的上,读进程一度终止。

14) SIGALRM
钟定时信号, 计算的是实际的岁月还是时钟时间. alarm函数使用该信号.

15) SIGTERM
次第结束(terminate)信号,
与SIGKILL不同之是拖欠信号好为卡住与拍卖。通常用来求程序自己健康退出,shell命令kill缺省来这信号。如果经过终止不了,我们才见面尝试SIGKILL。

17) SIGCHLD
旁进程结束时, 父进程会收此信号。
假定爸爸进程没有拍卖者信号,也并未等待(wait)子进程,子进程虽然平息,但是还会当基础进程表中据为己有表项,这时的子进程称为僵尸进程。这种情
况我们应当避免(父进程或忽视SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的停止自动由init进程
来接管)。

18) SIGCONT
为一个休(stopped)的进程继续执行. 本信号不克于阻塞.
可以为此一个handler来让程序在由stopped状态成为继续执行时得一定的工作.
例如, 重新显示提示称

19) SIGSTOP
停止(stopped)进程的执行.
注意她跟terminate以及interrupt的分别:该过程还免终止, 只是刹车执行.
本信号不克被死, 处理要忽略.

20) SIGTSTP
已进程的运行, 但该信号好为拍卖和忽略.
用户键入SUSP字符时(通常是Ctrl-Z)发出之信号

21) SIGTTIN
当后台作业要于用户终端读数据经常, 该学业中之持有进程会吸收SIGTTIN信号.
缺省时这些经过会已执行.

22) SIGTTOU
恍如于SIGTTIN, 但在形容终端(或涂改终端模式)时收到.

23) SIGURG
产生”紧急”数据或out-of-band数据达socket时产生.

24) SIGXCPU
越CPU时间资源限制. 这个范围好由getrlimit/setrlimit来读取/改变。

25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。

26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是测算的凡该过程占用的CPU时间.

27) SIGPROF
好像于SIGALRM/SIGVTALRM, 但包括该过程之所以之CPU时间及系统调用的时间.

28) SIGWINCH
窗口大小改变时发出.

29) SIGIO
文本讲述吻合准备妥当, 可以起来开展输入/输出操作.

30) SIGPWR
Power failure

31) SIGSYS
地下的体系调用。

关键点
每当上述列有底信号中,程序不得捕获、阻塞或忽视的信号有:SIGKILL,SIGSTOP
勿可知回升至默认动作的信号有:SIGILL,SIGTRAP
默认会招致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会造成进程退出的信号有:
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会招致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH
此外,SIGIO在SVR4是离,在4.3BSD着是忽视;SIGCONT在过程挂于时是后续,否则是忽视,不克于堵塞。
【具体由呢底,我吧无亮堂,问微信大神去把】

好了,说了那基本上,终于得以实战写代码了:
1.AppDelegate.m中

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.

InstallSignalHandler();//信号量截断
InstallUncaughtExceptionHandler();//系统异常捕获

return YES;
}

2.SignalHandler.m的实现

void SignalExceptionHandler(int signal)
{
    NSMutableString *mstr = [[NSMutableString alloc] init];
    [mstr appendString:@"Stack: "];
    void* callstack[128];
    int i, frames = backtrace(callstack, 128);
    char** strs = backtrace_symbols(callstack, frames);
    for (i = 0; i
        [mstr appendFormat:@"%s ", strs[i]];
    }
    [SignalHandler saveCreash:mstr];

}

void InstallSignalHandler(void)
{
    signal(SIGHUP, SignalExceptionHandler);
    signal(SIGINT, SignalExceptionHandler);
    signal(SIGQUIT, SignalExceptionHandler);

    signal(SIGABRT, SignalExceptionHandler);
    signal(SIGILL, SignalExceptionHandler);
    signal(SIGSEGV, SignalExceptionHandler);
    signal(SIGFPE, SignalExceptionHandler);
    signal(SIGBUS, SignalExceptionHandler);
    signal(SIGPIPE, SignalExceptionHandler);
}

3.UncaughtExceptionHandler.m的实现

void HandleException(NSException *exception)
{
    // 异常的堆栈信息
    NSArray *stackArray = [exception callStackSymbols];
    // 出现异常的原因
    NSString *reason = [exception reason];
    // 异常名称
    NSString *name = [exception name];
    NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@ Exception name:%@ Exception stack:%@",name, reason, stackArray];
    NSLog(@"%@", exceptionInfo);
    [UncaughtExceptionHandler saveCreash:exceptionInfo];
}

void InstallUncaughtExceptionHandler(void)
{
    NSSetUncaughtExceptionHandler(&HandleException);
}

4、测试一下
这边最紧要的同等步,SignalHandler不要在debug环境下测试。因为系统的debug会优先去阻拦。我们而运行一糟后,关闭debug状态。应该直接在模拟器上点击我们build上去的app去运作。而UncaughtExceptionHandler可以在调节状态下捕捉

- (void)buttonClick:(UIButton *)sender {
//1.信号量
    Test *pTest = {1,2};
    free(pTest);//导致SIGABRT的错误,因为内存中根本就没有这个空间,哪来的free,就在栈中的对象而已
    pTest->a = 5;
}
- (IBAction)buttonOCException:(UIButton *)sender
{
    //2.ios崩溃
    NSArray *array= @[@"tom",@"xxx",@"ooo"];
    [array objectAtIndex:5];
}

附上demo:【demo下载地址】

相关文章