慕课网 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)

改变思维方式

Single Source Of Truth: 单一数据源

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

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


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

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

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


一个进程有多个线程

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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)


总结:

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

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

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

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

1
2
brew install apr
brew install pcre
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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;
}

}

测试:

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

curl localhost:8080/stat

使用 Synchronized

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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;
}

}

测试:

1
2
3
4
5
使用synchronize后测试

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

curl localhost:8080/stat

使用 ThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@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;
}

}

测试:

1
2
3
4
5
使用ThreadLocal

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

curl localhost:8080/stat

总结

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@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;
}

}

测试:

1
2
3
4
5
6
7
使用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 是所有线程共享的,是个临界区

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

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

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

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

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

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

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

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

context 环境

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}
}

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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);
}

}

4-2 选学 HashTable (03:56)


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


第 5 章 全课总结

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

5-1 总结 (04:19)

一些建议

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

最后

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

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

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

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2023 高行行
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信