运行的时候,每个Player Thread都是去调用Attemp.guess()方法,进而操作同一个ThreadLocal变量history,但却可以保存每个线程自己的数据,这就是ThreadLocal的作用。
<span style="font-family:Courier New;">public class ThreadLocalTest {
public static void main(String[] args) {
Judge.prepare();
new Player(1).start();
new Player(2).start();
new Player(3).start();
}
}
class Judge {
public static int MAX_VALUE = 10;
private static int targetValue;
public static void prepare() {
Random random = new Random();
targetValue = random.nextInt(MAX_VALUE) + 1;
}
public static boolean judge(int value) {
return value == targetValue;
}
}
class Player extends Thread {
private int playerId;
public Player(int playerId) {
this.playerId = playerId;
}
@Override
public void run() {
boolean success = false;
while(!success) {
int value = Attempt.guess(Judge.MAX_VALUE);
success = Judge.judge(value);
System.out.println(String.format("Plyaer %s Attempts %s and %s", playerId, value, success ? " Success" : "Failed"));
}
Attempt.review(String.format("[IFNO] Plyaer %s Completed by ", playerId));
}
}
class Attempt {
private static ThreadLocal<Record> history = new ThreadLocal<Record>();
public static int guess(int maxValue) {
Record record = getRecord();
Random random = new Random();
int value = 0;
do {
value = random.nextInt(maxValue) + 1;
} while (record.contains(value));
record.save(value);
return value;
}
public static void review(String info) {
System.out.println(info + getRecord());
}
private static Record getRecord() {
Record record = history.get();
if(record == null) {
record = new Record();
history.set(record);
}
return record;
}
}
class Record {
private List<Integer> attemptList = new ArrayList<Integer>();;
public void save(int value) {
attemptList.add(value);
}
public boolean contains(int value) {
return attemptList.contains(value);
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(attemptList.size() + " Times: ");
int count = 1;
for(Integer attempt : attemptList) {
buffer.append(attempt);
if(count < attemptList.size()) {
buffer.append(", ");
count++;
}
}
return buffer.toString();
}
}</span>
运行结果:
Plyaer 1 Attempts 8 and Failed
Plyaer 1 Attempts 3 and Failed
Plyaer 1 Attempts 2 and Success
[IFNO] Plyaer 1 Completed by 3 Times: 8, 3, 2
Plyaer 3 Attempts 5 and Failed
Plyaer 3 Attempts 8 and Failed
Plyaer 3 Attempts 7 and Failed
Plyaer 3 Attempts 1 and Failed
Plyaer 3 Attempts 3 and Failed
Plyaer 3 Attempts 10 and Failed
Plyaer 3 Attempts 2 and Success
[IFNO] Plyaer 3 Completed by 7 Times: 5, 8, 7, 1, 3, 10, 2
Plyaer 2 Attempts 10 and Failed
Plyaer 2 Attempts 9 and Failed
Plyaer 2 Attempts 4 and Failed
Plyaer 2 Attempts 6 and Failed
Plyaer 2 Attempts 5 and Failed
Plyaer 2 Attempts 3 and Failed
Plyaer 2 Attempts 7 and Failed
Plyaer 2 Attempts 1 and Failed
Plyaer 2 Attempts 2 and Success
[IFNO] Plyaer 2 Completed by 9 Times: 10, 9, 4, 6, 5, 3, 7, 1, 2
关于ThreadLocal的原理,可以从其get()方法的实现来看
<span style="font-family:Courier New;">public class ThreadLocal<T> {
...
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
...
}</span>
执行get()时首先获取当前的Thread,再获取Thread中的ThreadLocalMap - t.threadLocals,并以自身为key取出实际的value。于是可以看出,ThreadLocal的变量实际还是保存在Thread中的,容器是一个Map,Thread用到多少ThreadLocal变量,就会有多少以其为key的Entry。
总结
1、ThreadLocal是使用的一种线程的局部变量。
2、使用ThreadLocal可以很好每次都使用开始创建的那个对象。
3、Hibernate中就使用ThreadLocal来获取当前事务的处理。
4、ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。
比如实际项目中我不想使用MVC框架,而使用自己简易线程安全的MVC框架,可以将request和response对象存放在ThreadLocal对象中,这样访问的就是已存在的request和response变量:
/**
* 将request,response放到ThreadLocal方便普通java类调用
*/
public class MyCustomContext {
private static ThreadLocal<HttpServletRequest> myRequest = new ThreadLocal<HttpServletRequest>();
private static ThreadLocal<HttpServletResponse> myResponse = new ThreadLocal<HttpServletResponse>();
/**
* 获取当前登录用户
*/
public static SysUserEntity getLoginUser() {
return (SysUserEntity) getMySession().getAttribute("login_user");
}
/**
* 将request、response放入ThreadLocal容器
*/
public static void setAll(HttpServletRequest request,HttpServletResponse response){
myRequest.set(request);
myResponse.set(response);
}
/**
* 获得request
* @return
*/
public static HttpServletRequest getMyRequest(){
return myRequest.get();
}
/**
* 获得session
*/
public static HttpSession getMySession(){
HttpServletRequest request = getMyRequest();
if(null == request){
throw new MyRuntimeException("myRequest未赋值,无法获取session");
}
return request.getSession();
}
public static void setMyRequest(HttpServletRequest request){
myRequest.set(request);
}
/**
* 获得response
*/
public static HttpServletResponse getMyResponse(){
return myResponse.get();
}
public static void setMyRequest(HttpServletResponse response){
myResponse.set(response);
}
public static void removeMyRequest(){
myRequest.remove();
}
public static void removeMyResponse(){
myResponse.remove();
}
public static void remoceAll(){
myRequest.remove();
myResponse.remove();
}
}