用过EventBus的同学都知道,在接收发出事件时,会有四种线程模式。那他是内部是怎样实现的呢?近日忙里偷闲,简单看了下EventBus源码,发现其发送事件前检查发送事件的线程状态(源码标注处),然后根据postingState去做一些线程切换的操作。
扯了半天发现跟标题ThreadLocal没半毛钱关系,哈哈哈。显然不可能,currentPostingThreadState其实就是一个ThreadLocal。终于步入正题了,本文将结合源码对ThreadLocal的使用及原理进行简单剖析,大致分为以下3个模块:
一、ThreadLocal是什么
二、ThreadLocal怎么用
三、源码分析
以下如果有不恰当或者错误的地方,还希望各位大佬指点!
一、ThreadLocal是什么
ThreadLocal,顾名思义,线程本地变量,也有叫线程本地存储的,差不多意思。在日常开发中,我们经常会遇到多线程的场景,比如数据库连接操作,都需要对connection进行持有。在这类场景下,出于线程安全的考虑,一般有两种做法:
1、方法加锁,同步操作。线程排队,依次执行。这种方法一定程度上影响程序执行效率,因为一个线程在使用共享变量时,其他线程只有等待。(时间换空间)
2、ThreadLocal实现。ThreadLocal在每个线程中将共享变量会创建一个副本,所有操作都是基于线程自己的副本变量,不影响其他线程,既能解决线程安全问题,又不会影响程序执行性能。缺点是创建副本时,对资源消耗增加(空间换时间)
二、ThreadLocal怎么用
Talk is cheap,show your code
1、构建一个模拟数据库连接的Connection实体,两个属性,id为整型,name为String型
public class Connection {
private int id; //数据库连接id
private String name; //数据库连接名称
public Connection(int id,String name){
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Connection{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
2、数据库连接管理类,使用单例模式,一个get()、set()方法,实际内部就是将数据从ThreadLocal获取和存储。
public class ConnectionManager {
private static ConnectionManager sInstance;
//使用ThreadLocal存储Connection
private ThreadLocal<Connection> connection = new ThreadLocal<>();
private ConnectionManager(){}
public static ConnectionManager getInstance(){
if (sInstance==null){
synchronized (ConnectionManager.class){
if (sInstance == null) {
sInstance = new ConnectionManager();
}
}
}
return sInstance;
}
public Connection getConnection() {
return connection.get();
}
public void setConnection(Connection connection) {
this.connection.set(connection);
}
}
3、准备工作完毕,写个main方法测试一下。这里其实是模拟三个线程操作数据库,一个主线程,两个子线程,执行完毕,将其连接的属性打印出来。
public class TestThreadLocal {
public static void main(String[] args) throws InterruptedException {
//在主线程创建连接
ConnectionManager.getInstance().setConnection(new Connection(1,"主线程连接"));
System.out.println(ConnectionManager.getInstance().getConnection().toString());
//在子线程1创建连接
Thread thread1 = new Thread(){
@Override
public void run() {
super.run();
ConnectionManager.getInstance().setConnection(new Connection(2,"thread1连接"));
System.out.println(ConnectionManager.getInstance().getConnection().toString());
}
};
thread1.start();
//堵塞主线程
thread1.join();
//在子线程1创建连接
Thread thread2 = new Thread(){
@Override
public void run() {
super.run();
ConnectionManager.getInstance().setConnection(new Connection(3,"thread2连接"));
System.out.println(ConnectionManager.getInstance().getConnection().toString());
}
};
thread2.start();
//堵塞主线程
thread2.join();
System.out.println(ConnectionManager.getInstance().getConnection().toString());
}
}
执行完毕,打印log如下。显然可以看出各个线程互不影响,达到了线程安全的目的。
当然这里有个注意点,在使用ThreadLcoal的get方法前必须进行set,否则会报空指针异常。是不是感觉不够优雅,其实ThreadLocal还提供了initialValue()方法,重写该方法,初始化数据,也可以达到set的效果。这里就不再赘述。
三、源码分析
大致思路:ThreadLocal能够实现了线程之前互不影响,资源独立,其实归根究底是ThreadLocalMap(ThreadLocal的内部类)的功劳。是因为每个Thread有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap,key是ThreadLocal包装成的一个弱引用,value为代码中实际放入的各种类型的值。当线程set或者get值的时候,都会在自己的ThreadLocalMap里操作。
关于ThreadLocal内部详细实现,请参见https://www.cnblogs.com/micrari/p/6790229.html