基础概念

三种方式创建线程

1.继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
class task extends Thread {
@Override
public void run() {
System.out.println("Thread子类重写方法创建线程");
}
}

public class test {
public static void main(String[] args) {
task t1 = new task();
t1.start();
}
}

2.实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class task implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable接口方法创建线程");
}
}

public class test {
public static void main(String[] args) {
task t1 = new task();
Thread thread1 = new Thread(t1);
thread1.start();
}
}

Lambda 表达式实现 Runnable 接口

1
2
3
Thread t1 = new Thread(() -> {
System.out.println("线程1运行中");
});

3.实现Callable接口

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
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class task implements Callable<String> {
@Override
public String call() throws Exception {
return "实现Callable接口创建线程";
}
}

public class test {
public static void main(String[] args) {
task t1 = new task();
FutureTask futureTask = new FutureTask(t1);
Thread thread1 = new Thread(futureTask);
thread1.start();
try {
futureTask.get();
System.out.println(futureTask.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}

可见,Runnable接口和Callable接口的实现类并不直接具有启动线程的能力需要借助Thread类启动。

这三者的使用场景分别是

Thread:单继承

Runnable:无返回值任务

Callable:有返回值任务

常用方法

获取/设置当前线程名:getName,setName

获取/设置当前线程优先级:getPriority,setPriority

优先级越高的线程,获得CPU资源的概率会越大,并不是说一定优先级越高的线程越先执行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class test {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("我是线程1");
});
Thread t2 = new Thread(() -> {
System.out.println("我是线程2");
});
Thread t3 = new Thread(() -> {
System.out.println("我是线程3");
});
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.NORM_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}

image-20250617202208351

currentThread方法:获取当前线程的对象

打印这个对象,可以看见输入分别是[线程名,优先级,所属线程组]

image-20250617195858652

这是Thread类的toString方法

image-20250617200618017

run方法和start方法的区别

run方法重写自Runnable接口。start方法后会自动调用run方法。

image-20250617201059850

sleep方法和wait方法

sleep方法使线程休眠,不释放锁。

wait方法为Object类的方法,释放锁。

第一个参数单位为毫秒,第二个参数可选,为纳秒

如何停止线程

通过interrupt方法设置中断信号,在线程里面自己实现响应中断。

image-20250617202832536

如果是停止休眠中的线程会抛出异常。

线程的礼让和加入

使当前线程放弃执行权:yield方法

一个线程等待另一个线程执行完成后再继续进行(调用join方法的线程的插队):join方法

后台线程

setDemon方法:设置是否为后台线程,true为是,false为否

isDemon方法:判断线程是否为后台线程,返回true为是,false为否。

判断线程是否存活:isAlive方法,返回true为是,false为否。

ThreadLocal

作用

设计理念是线程隔离,通过为每个线程提供共享变量的变量副本,来解决多线程环境下的共享问题。

通过以下例子可看出,ThreadLocal是线程隔离的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class test {
static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
Thread thread0 = new Thread(() -> {
System.out.println(threadLocal.get());
threadLocal.set(0);
System.out.println(threadLocal.get());

});
Thread thread1 = new Thread(() -> {
System.out.println(threadLocal.get());
threadLocal.set(1);
System.out.println(threadLocal.get());

});
thread0.start();
thread1.start();
}
}

image-20250618144546864

注意事项:

ThreadLocal 的核心目的是线程隔离,而不是线程安全地修改共享变量。

ThreadLocal的局限性:只适用于单机多线程环境,分布式系统中不同节点的ThreadLocal不共享

ThreadLocal 实现原理

ThreadLocal 的实现原理可以分为三个核心部分来理解:

  1. 数据结构设计
    Thread 与 ThreadLocalMap 的关系
1
2
3
// 在Thread类中
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

每个Thread对象‌内部都维护了一个ThreadLocalMap实例
这个Map是延迟初始化的,只有在第一次调用ThreadLocal的set/get时才会创建
ThreadLocalMap 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // key是弱引用的ThreadLocal对象
value = v;
}
}

private Entry[] table;
private int size;
private int threshold;
// ...

}

这是一个定制化的哈希表,专门为ThreadLocal优化
Entry继承自WeakReference,key是弱引用的ThreadLocal对象
value是强引用存储的实际值,即变量副本

  1. 核心操作原理
    set() 方法原理
1
2
3
4
5
6
7
8
9
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap
if (map != null) {
map.set(this, value); // this指当前ThreadLocal对象
} else {
createMap(t, value); // 首次使用,创建Map
}
}

获取当前线程的ThreadLocalMap
如果Map存在,以当前ThreadLocal对象为key存储值
如果Map不存在,先创建Map再存储
get() 方法原理

1
2
3
4
5
6
7
8
9
10
11
12
13
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); // 返回初始值(null或重写的initialValue)
}

获取当前线程的ThreadLocalMap
以当前ThreadLocal对象为key查找Entry
找到则返回值,找不到则初始化
remove() 方法原理

1
2
3
4
5
6
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this); // 移除当前ThreadLocal对应的Entry
}
}

获取当前线程的ThreadLocalMap
移除当前ThreadLocal对象对应的Entry

  1. 哈希冲突解决

ThreadLocalMap使用‌线性探测法(开放寻址法)‌解决冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 计算初始位置


// 线性探测
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // key已存在,更新value
e.value = value;
return;
}
if (k == null) { // 遇到过期Entry,替换
replaceStaleEntry(key, value, i);
return;
}
}
// 找到空槽,创建新Entry
tab[i] = new Entry(key, value);
// ...

}

计算初始位置,如果位置有值就顺序往后移,直到找到空key。

内存管理机制
弱引用设计

1
2
3
4
5
6
7
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // key是弱引用
value = v; // value是强引用
}
}

key弱引用‌:当ThreadLocal对象失去强引用时,可以被GC回收
value强引用‌:需要手动remove或等待线程结束才能释放
过期Entry清理
探测式清理‌:在set/get时遇到过期Entry会触发清理
启发式清理‌:在扩容时会扫描整个表清理过期Entry
被动清理‌:线程终止时,整个ThreadLocalMap会被回收
完整工作流程示例

线程A首次调用threadLocalA.set(value1)

创建线程A的ThreadLocalMap
存入Entry: key=threadLocalA, value=value1

线程A调用threadLocalB.set(value2)

使用已有ThreadLocalMap
存入新Entry: key=threadLocalB, value=value2

线程B调用threadLocalA.set(value3)

创建线程B的ThreadLocalMap
存入Entry: key=threadLocalA, value=value3
(与线程A的value1互不影响)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class test {

static ThreadLocal threadLocalA = new ThreadLocal();
static ThreadLocal threadLocalB = new ThreadLocal();

public static void main(String[] args) {

Thread threadA = new Thread(() -> {
threadLocalA.set("线程A的变量副本A");
threadLocalB.set("线程A的变量副本B");
System.out.println(threadLocalA.get()+" "+threadLocalB.get());
});
Thread threadB = new Thread(() -> {
threadLocalA.set("线程B的变量副本A");
threadLocalB.set("线程B的变量副本B");
System.out.println(threadLocalA.get()+" "+threadLocalB.get());
});
threadA.start();
threadB.start();
}
}

image-20250618154200181

当threadLocalA=null且被GC回收后

线程A的Map中对应Entry的key变为null
但value1仍然存在,直到主动remove或线程结束。

内存泄漏的情况

1. 长期存活线程且未调用remove方法

场景‌:

1
2
3
4
5
6
7
8
9
10
javaCopy Code// 使用线程池
ExecutorService executor = Executors.newFixedThreadPool(5);

ThreadLocal<BigObject> threadLocal = new ThreadLocal<>();

executor.execute(() -> {
threadLocal.set(new BigObject()); // 大对象
// 执行业务逻辑...
// 但没有调用 threadLocal.remove()
});

泄漏原因‌:

  • 线程池中的线程会长期存活,即使业务逻辑完成,ThreadLocalMap中的Entry仍然存在
  • 下次该线程执行其他任务时,旧的BigObject仍然占用内存

解决:

对于线程池场景,使用beforeExecute/afterExecute

1
2
3
4
5
executor = new ThreadPoolExecutor(...) {
protected void afterExecute(Runnable r, Throwable t) {
threadLocal.remove();
}
};