ThreadLocal的使用方法

本文介绍ThreadLocal的用途、原理及其在解决多线程并发访问时数据不一致问题的应用案例。通过一个游戏场景展示了如何利用ThreadLocal记录玩家猜测历史,确保每个线程的数据独立性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ThreadLocal的含义是Thread Local Variable,它可以声明一个字段,使得不同的线程访问这个字段时,获取的都是当前线程的变量,互不影响。ThreadLocal的作用和在每个Thread类声明一个字段相同,那么什么时候使用它呢?还是在编写一些框架时,因为这时你无法预先定义Thread类。其中一个典型的用法是调用一个静态方法,这个静态方法会操作一个ThreadLocal变量,而不同的线程去调用时,访问的就是不同的副本。

下面通过这样一个例子来说明:模拟一个游戏,预先随机设定一个[1, 10]的整数,然后每个玩家去猜这个数字,每个玩家不知道其他玩家的猜测结果,看谁用最少的次数猜中这个数字。

这个游戏确实比较无聊,不过这里恰好可以把每个玩家作为一个线程,然后用ThreadLocal来记录玩家猜测的历史记录,这样就很容易理解ThreadLocal的作用。

Judge:用来设定目标数字以及判断猜测的结果。
Player:每个Player作为一个线程,多个Player并行地去尝试猜测,猜中时线程终止。
Attempt:具有ThreadLocal字段和猜测动作静态方法的类,ThreadLocal用于保存猜过的数字。
Record:保存历史记录的数据结构,有一个List<Integer>字段。

ThreadLocal为了可以兼容各种类型的数据,实际的内容是再通过set和get操作的对象,详见Attempt的getRecord()。

运行的时候,每个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使用场合主要解决多线程中数据数据因并发产生不一致问题。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。Synchronized用于线程间的数据共享,而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();
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sg_0504

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值