基础概念
三种方式创建线程
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(); } }
|

currentThread方法:获取当前线程的对象
打印这个对象,可以看见输入分别是[线程名,优先级,所属线程组]

这是Thread类的toString方法

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

sleep方法和wait方法
sleep方法使线程休眠,不释放锁。
wait方法为Object类的方法,释放锁。
第一个参数单位为毫秒,第二个参数可选,为纳秒
如何停止线程
通过interrupt方法设置中断信号,在线程里面自己实现响应中断。

如果是停止休眠中的线程会抛出异常。
线程的礼让和加入
使当前线程放弃执行权: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(); } }
|

注意事项:
ThreadLocal 的核心目的是线程隔离,而不是线程安全地修改共享变量。
ThreadLocal的局限性:只适用于单机多线程环境,分布式系统中不同节点的ThreadLocal不共享
ThreadLocal 实现原理
ThreadLocal 的实现原理可以分为三个核心部分来理解:
- 数据结构设计
Thread 与 ThreadLocalMap 的关系
1 2 3
| 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); value = v; } } private Entry[] table; private int size; private int threshold;
}
|
这是一个定制化的哈希表,专门为ThreadLocal优化
Entry继承自WeakReference,key是弱引用的ThreadLocal对象
value是强引用存储的实际值,即变量副本
- 核心操作原理
set() 方法原理
1 2 3 4 5 6 7 8 9
| public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } }
|
获取当前线程的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(); }
|
获取当前线程的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); } }
|
获取当前线程的ThreadLocalMap
移除当前ThreadLocal对象对应的Entry
- 哈希冲突解决
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) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } 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); value = v; } }
|
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(); } }
|

当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(); } };
|