慕课网 ThreadLocal 教学视频学习笔记

课程地址:https://www.imooc.com/learn/1217

作者:求老仙奶我不到P10(这昵称🤔,我奶一口,你到不了P10 🐶)

作者简介:我是一名有10年经验的互联网老兵,创过业、也曾任数家大型互联网公司架构师、团队Leader,30岁(2018)任职阿里巴巴高级技术专家(P8)。曾负责架构PHP高负载、前端(React/RN)方向、Java领域化中间件方向、大数据(BI和数据可视化)等多方向业界知名项目。大学刷完算法导论,参加过ACM和国际机器人竞赛,多年在项目中实践技术驱动、前端后端领域化、数据可视化,多个领域实战经验丰富。

代码地址(跟着课程手敲的):https://github.com/gaohanghang/spring-threadlocal-demo

简介:多线程增加了我们的不确定性,破坏了可预测性——当然,这对于【艺高人胆大】的未来的你,都是小事,因为你会不断进步成长,只要你把握好现在的光阴。科学的美,在于它的模型可以不断的迭代和进步,Java是一种简化和进步,ThreadLocal也一种简化和进步,如同Java给编程带来了很多安全感,而ThreadLocal给多线程时代带了更多的安全感(可预测性、确定性,一致性……)。课程是一种爬坡训练,难度会一直上去直到你完全理解,可以自己动手实现。

思维导图:

第1章 纵观课程纲要

了解一致性等基础概念,解决一致性的基本方法,把ThreadLocal放到一个宏观背景去思考。

1-1 论程序的安全感 (09:09)

image.png

改变思维方式

image.png
image.png
image.png

Single Source Of Truth: 单一数据源

不断的解决数据不一致的问题

image.png
image.png

1-2 课程介绍 (07:12)

image.png
image.png

image.png
image.png

第2章 是什么?怎么用?何时用?如何不出问题?

手把手带着Coding,解决基本概念、API以及讲4个关键应用场景。以及工作中并发场景,如何不出问题。

2-1 ThreadLocal是什么 (07:41)

image.png

一个进程有多个线程

定义:提供线程局部变量;一个线程局部变量在多个线程中,分别有独立的值(副本)

特点:简单(开箱即用)、快速(无额外开销)、安全(线程安全)

场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等场景)

image.png
image.png

进程 -> 线程表 -> 线程局部变量

image.png
image.png
image.png

2-2 ThreadLocal基本API (07:52)

image.png
public class Basic {

    // ThreadLocal<T>
    public static ThreadLocal<Long> x = new ThreadLocal<Long>(){
        @Override
        protected Long initialValue() {
            System.out.println("Initial Value run..");
            return Thread.currentThread().getId();
        }
    };

    public static void main(String[] args) {

        new Thread() {
            @Override
            public void run() {
                System.out.println(x.get());
            }
        }.start();
        x.set(107L);
        // 移除当前线程上的ThreadLocal的值
        x.remove();
         /*
            get操作会延迟加载,如果不get,不会触发initialValue
         */
        System.out.println(x.get());

    }

}

2-3 ThreadLocal的4种核心场景 (07:51)

image.png
image.png
image.png
image.png
image.png
image.png

总结:

  • 持有资源——持有线程资源供线程的各个部分使用,全局获取,减少编程难度
  • 线程一致——帮助需要保持线程一致的资源(如数据库事务)维护一致性,降低编程难度
  • 线程安全——帮助只考虑了单线程的程序库,无缝向多线程场景迁移
  • 分布式计算——帮助分布式计算场景的各个线程累计局部计算结果。

2-4 Thread Local并发场景分析01 (16:50)

代码地址:https://github.com/gaohanghang/spring-threadlocal-demo

image.png

MAC系统上安装Apache ab测试工具教程地址: https://www.cnblogs.com/cjsblog/p/10506647.html

brew install apr
brew install pcre
@RestController
public class StartController {
    
    static Integer c = 0;
   
    @RequestMapping("/stat")
    public Integer stat() {
        return c;
    }
    @RequestMapping("/add")
    public Integer add() throws InterruptedException {
        Thread.sleep(100);
        c++;
        return 1;
    }
    
}

测试:

ab -n 10000 -c 1 localhost:8080/add

curl localhost:8080/stat
image.png
image.png

使用 Synchronized

@RestController
public class StartController {

    static Integer c = 0;

    synchronized void  __add() throws InterruptedException {
        Thread.sleep(100);
        c++;
    }

    @RequestMapping("/stat")
    public Integer stat() {
        return c;
    }

    @RequestMapping("/add")
    public Integer add() throws InterruptedException {
        //Thread.sleep(100);
        //c++;
        __add();
        return 1;
    }

}

测试:

使用synchronize后测试

ab -n 100 -c 100 localhost:8080/add

curl localhost:8080/stat

使用ThreadLocal

@RestController
public class StartController {

    static ThreadLocal<Integer> c = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    void __add() throws InterruptedException {
        Thread.sleep(100);
        c.set(c.get() + 1);
    }

    @RequestMapping("/stat")
    public Integer stat() {
        return c.get();
    }

    @RequestMapping("/add")
    public Integer add() throws InterruptedException {
        __add();
        return 1;
    }

}

测试:

使用ThreadLocal

ab -n 10000 -c 100 localhost:8080/add

curl localhost:8080/stat
image.png

总结

  • 基于线程池模型synchronize(排队操作很危险
  • 用ThreadLocal收集数据很快且安全
  • 思考:如何在多个ThreadLocal中收集数据?

2-5 ThreadLocal场景分析——减少同步 (10:10)

image.png
@RestController
public class StartController {

    static HashSet<Val<Integer>> set = new HashSet<>();

    synchronized static void addSet(Val<Integer> v) {
        set.add(v);
    }

    static ThreadLocal<Val<Integer>> c = new ThreadLocal<Val<Integer>>() {
        @Override
        protected Val<Integer> initialValue() {
            Val<Integer> v = new Val<>();
            v.set(0);
            addSet(v);
            return v;
        }
    };

    void __add() throws InterruptedException {
        Thread.sleep(100);
        Val<Integer> v = c.get();
        v.set(v.get() + 1);
    }

    @RequestMapping("/stat")
    public Integer stat() {
        return set.stream().map(x -> x.get()).reduce((a,x) -> a+x).get();
    }

    @RequestMapping("/add")
    public Integer add() throws InterruptedException {
        __add();
        return 1;
    }

}

测试:

使用ThreadLocal

ab -n 10000 -c 100 localhost:8080/add

ab -n 10000 -c 200 localhost:8080/add

curl localhost:8080/stat

问题:set.add(v); 为什么会有线程安全问题

set是所有线程共享的,是个临界区

image.png

第3章 【极客视角】大神们怎么用ThreadLocal的

挑选了3个Java领域影响深远的应用,Spring/Mybatis/Quartz中使用到ThreadLocal的源码,理解大神们在思考什么,为什么会用到ThreadLocal。

3-1 源码分析1-Quartz SimpleSemaphore (06:42)

image.png
image.png

3-2 源码分析2 Mybatis框架保持连接池线程一致 (04:46)

image.png
image.png

A(Atomic))原子性,操作不可分割。
C(Consistency)一致性,任何时刻数据都能保持一致。
I(Isolation)隔离性,多事务并发执行的时序不影响结果。
D(Durability)持久性,对数据结构的存储是永久的。

image.png
image.png

3-3 源码分析03 Spring框架对分布式事务的支持 (04:03)

image.png

A(Atomic))原子性,操作不可分割。
C(Consistency)一致性,任何时刻数据都能保持一致。
I(Isolation)隔离性,多事务并发执行的时序不影响结果。
D(Durability)持久性,对数据结构的存储是永久的。

A:要么发生,要么不发生
C:要做对,做错了不算
I:同时发生两个事务,两个事务的结果能够叠加的一致,同时扣款
D:数据要被持久化下来

image.png
image.png

context 环境

第4章 【设计者视角】源码级实现&源码分析

4-1 实现自己的ThreadLocal (12:16)

public class MyThreadLocal<T> {

    // 共享空间
    static HashMap<Thread, HashMap<MyThreadLocal<?>, Object>> threadLocalMap = new HashMap<>();

    // 临界区

    /**
     * 获取当前线程的数据
     * @return
     */
    synchronized static HashMap<MyThreadLocal<?>, Object> getMap() {
        // 获取当前线程
        Thread thread = Thread.currentThread();
        // 判断threadLocalMap是否包含当前线程,不包含就put进去
        if (!threadLocalMap.containsKey(thread)) {
            threadLocalMap.put(thread, new HashMap<MyThreadLocal<?>,Object>());
        }
        // 获取当前thread的map
        return threadLocalMap.get(thread);
    }

    protected T initialValue() {
        return null;
    }

    public T get() {
        HashMap<MyThreadLocal<?>, Object> map = getMap();
        if (!map.containsKey(this)) {
            map.put(this, initialValue());
        }
        return (T) map.get(this);
    }

    public void set(T v) {
        HashMap<MyThreadLocal<?>, Object> map = getMap();
        map.put(this, v);
    }

}
public class Test {

    static MyThreadLocal<Long> v = new MyThreadLocal<Long>() {
        @Override
        protected Long initialValue() {
            return Thread.currentThread().getId();
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(v.get());
            }).start();
        }
    }

}
image.png

image.png
public class MyThreadLocal<T> {

    static AtomicInteger atomic = new AtomicInteger();

    // 自增
    Integer threadLocalHash = atomic.addAndGet(0x61c88647);

    // 共享空间
    static HashMap<Thread, HashMap<Integer, Object>> threadLocalMap = new HashMap<>();

    // 临界区

    /**
     * 获取当前线程的数据
     * @return
     */
    synchronized static HashMap<Integer, Object> getMap() {
        // 获取当前线程
        Thread thread = Thread.currentThread();
        // 判断threadLocalMap是否包含当前线程,不包含就put进去
        if (!threadLocalMap.containsKey(thread)) {
            threadLocalMap.put(thread, new HashMap<Integer,Object>());
        }
        // 获取当前thread的map
        return threadLocalMap.get(thread);
    }

    protected T initialValue() {
        return null;
    }

    public T get() {
        HashMap<Integer, Object> map = getMap();
        if (!map.containsKey(this.threadLocalHash)) {
            map.put(this.threadLocalHash, initialValue());
        }
        return (T) map.get(this.threadLocalHash);
    }

    public void set(T v) {
        HashMap<Integer, Object> map = getMap();
        map.put(this.threadLocalHash, v);
    }

}
image.png

4-2 选学HashTable (03:56)

image.png
image.png

image.png
image.png
image.png
image.png
image.png

4-3 ThreadLocal源码分析 (13:40)

image.png

image.png
image.png

第5章 全课总结

ThreadLocal只是一个简单的数据结构,却引出了这么多问题,可见真理常常隐藏在容易忽视微小的地方,优秀的程序员不仅仅要大局观强,还更加需要磨砺细节——和老师一起思考未来应该怎样学习?

5-1 总结 (04:19)

image.png
image.png
image.png
image.png

一些建议

  • 无论将来到什么样的高度,永远认为自己是个菜鸡:总有比我们厉害的大牛,永远都去学习
  • 保持兴趣,体会乐趣:投入在工作上的时间是很多的,在工作上一定要保持兴趣
  • 技术创造是有价值的(切记):自己尝试去创造,尝试学新东西去用

最后

首先感谢大佬的课程分享,通过课程学到了很多东西

另外是我有个公众号,叫《骇客与画家》,欢迎大家来关注 😆

骇客与画家,名称来自书籍《黑客与画家》,画家学习绘画的方法是动手去画,骇客学习编程的方法也是动手去实践

image.png

个人公众号《骇客与画家》,欢迎关注

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容