多线程场景下需要注意线程安全. 一种方法是给竞争资源上锁, 该资源在某个时刻仅能被某一个线程占用; 另一种方法就是大家都使用各自的资源, 不要相互干扰, 我们可以借助 TheadLocal 来实现.
模拟场景: 我们要做一个ID生成器, 要求多线程使用的时候, 每个线程可以独立工作生成自增的ID.
/**
* ID 生成器
*/
public class IdGenerator {
private static ThreadLocal id = new ThreadLocal() {
@Override
protected Long initialValue() {
return 0L;
}
};
/**
* 获得一个新的 ID
* @return
*/
public Long getNumber() {
id.set(id.get() + 1);
return id.get();
}
}
/**
* 独立任务, 要求在任务内获取从1开始的自增ID
*/
class Task implements Runnable {
private IdGenerator idGenerator;
public Task(IdGenerator idGenerator) {
this.idGenerator = idGenerator;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + idGenerator.getNumber());
}
}
}
调用:
public class Main {
public static void main(String[] args) {
IdGenerator idGenerator = new IdGenerator();
Thread[] threads = new Thread[3];
for (int i = 0; i < threads.length; i++) {
Task task = new Task(idGenerator);
threads[i] = new Thread(task);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
}
实现 ThreadLocal
从以上例子看出, ThreadLocal 的核心就是让每个线程使用独立的(隔离的)资源, 我们可以用 Hash 来模拟这个过程:
public class DiyThreadLocal {
private Map data = Collections.synchronizedMap(new HashMap());
private T initialValue;
public void set(T value) {
data.put(currentKey(), value);
}
public T get() {
if (!data.containsKey(currentKey())) {
set(initialValue());
}
return data.get(currentKey());
}
public void remove() {
data.remove(currentKey());
}
/**
* 默认初始值为null, 通过重载该方法来设置默认值.
*
* @return
*/
protected T initialValue() {
return null;
}
/**
* 以当前的线程作为Map的键
*
* @return
*/
private Thread currentKey() {
return Thread.currentThread();
}
}
之后把 IdGenerator 类中使用的 ThreadLocal 替换成 DiyThreadLocal, 可以得到相同的效果.
OUTPUT:
Thread-0:1
Thread-1:1
Thread-2:1
Thread-1:2
Thread-0:2
Thread-1:3
Thread-2:2
Thread-1:4
Thread-0:3
Thread-1:5
Thread-2:3
Thread-0:4
Thread-2:4
Thread-0:5
Thread-2:5
大部分情况下使用 TheadLocal 都是不得已, 受害于依赖的类对 static 的不恰当使用. 要求相互不干扰的资源就应该使用成员变量, 自作聪明使用静态化只会导致线程不安全.