概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。 ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程局部变量并不是Java的新发明,很多语言(如IBM XL、FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供语言级支持,而以一种变通的方法,通过ThreadLocal的类提供支持。所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,这也是为什么线程局部变量没有在Java开发者中得到很好普及的原因。
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下。
- void set(Object value) 设置当前线程的线程局部变量的值;
- public Object get() 该方法返回当前线程所对应的线程局部变量;
- public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
- protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:
代码清单9-3 SimpleThreadLocal
public class SimpleThreadLocal {
private Map valueMap = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
//①键为线程对象,值为本线程的变量副本
valueMap.put(Thread.currentThread(), newValue);
}
public Object get() {
Thread currentThread = Thread.currentThread();
//②返回本线程对应的变量
Object o = valueMap.get(currentThread);
//③如果在Map中不存在,放到Map中保存起来
if (o == null && !valueMap.containsKey(currentThread)) {
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public void remove() {
valueMap.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}
一个ThreadLocal实例
package com.baobaotao.basic;
public class SequenceNumber {
//①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue(){
return 0;
}
};
//②获取下一个序列值
public int getNextNum(){
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[ ] args)
{
SequenceNumber sn = new SequenceNumber();
//③ 3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread
{
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this.sn = sn;
}
public void run()
{
//④每个线程打出3个序列值
for (int i = 0; i < 3; i++) {
System.out.println("thread["+Thread.currentThread().getName()+
"] sn["+sn.getNextNum()+"]");
}
}
}
}
通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:
thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-2] sn[2]
thread[Thread-0] sn[2]
thread[Thread-1] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[3]
thread[Thread-1] sn[3]
考查输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个Sequence Number实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本
与Thread同步机制的比较
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度上简化ThreadLocal的使用,代码清单9-2就使用了JDK 5.0新的ThreadLocal<T>版本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
分享到:
相关推荐
`Values` 是 ThreadLocal 的内部类,它实际上实现了一个简易的 HashMap 功能,以键值对的形式存储数据。 - **set() 方法**:当调用 `set(T value)` 时,会获取当前线程,然后通过 `values(currentThread)` 获取或...
以上是对ThreadLocal的简单介绍和源码分析,实际使用中还需要结合具体业务场景进行合理设计和优化。通过深入理解ThreadLocal的工作原理,可以帮助我们更好地利用这一工具,提高代码的并发性能和可维护性。
### 苍穹外卖系统中的ThreadLocal应用:共享当前登录用户ID #### 一、背景介绍 在现代Web应用开发中,特别是在微服务架构下,如何有效地...`ThreadLocal`作为一种简单而有效的工具,在实际项目中有着广泛的应用前景。
以上就是Spring Boot与MyBatis多数据源整合的简单介绍,实际项目中可能还需要考虑事务管理、数据源自动切换等问题。通过合理的抽象和设计,我们可以构建出高效、灵活的多数据源解决方案。在提供的压缩包文件`...
在Java 2(J2SE)中,`java.lang`包添加了一些新的类和方法,如`InheritableThreadLocal`,`ThreadLocal`的扩展,使得子线程可以继承父线程的变量,以及`SecurityManager`,用于实现安全管理策略。 过时方法在Java ...
通过实现`SingleThreadModel`接口、使用`synchronized`关键字以及利用`ThreadLocal`类等方法,可以在不同程度上解决这一问题。然而,在选择具体的实现方式时,还需要根据具体的应用场景和性能需求来权衡利弊。随着...
这种方式简单直接,但每个线程类只能实现一个Runnable接口,限制了代码的复用性。 2. 实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法。然后将Runnable对象作为参数传递给Thread类的构造器,创建...
6. **线程与并发**:Java提供强大的并发支持,讲义会讲解线程的创建、同步和通信,包括synchronized关键字、wait()、notify()方法和ThreadLocal类的应用。 7. **网络编程**:Java的Socket编程是进行网络通信的基础...
3. **多线程**:涵盖线程的创建、同步、通信,以及ThreadLocal和并发工具类的使用。 4. **网络编程**:介绍套接字(Socket)编程,以及服务器端和客户端的实现。 5. **反射机制**:讲解如何在运行时动态获取类的...
- **内部实现**:ThreadLocal类内部维护了一个HashMap类型的成员变量,用于存储每个线程的变量副本。键为线程对象,值为对应线程的变量副本。 - **应用场景**:ThreadLocal广泛应用于各种多线程编程场景中,如: - ...
响应线程中断应正确处理InterruptedException异常,不应简单地忽略或重新抛出。创建线程时要注意提供线程名,否则在多线程环境中排查问题时会非常困难。 在多线程编程中,还应当避免创建不必要的线程,因为线程的...
- **线程变量管理器**:通过ThreadLocal类来实现线程变量的管理,以解决多线程环境下的数据共享问题。 - **事务管理**:深入讨论了事务的基本概念、为何需要事务、事务的隔离级别等内容,并重点介绍了Spring框架中...
ThreadLocal<Integer> threadLocal = new ThreadLocal(); threadLocal.set(0); // 每个线程都可以设置自己的值 int value = threadLocal.get(); // 获取当前线程的值 ``` #### 5.2 使用阻塞队列的生产者-消费者...
6. **网络编程**:Java在网络编程方面有强大的支持,书中介绍了Socket编程,包括TCP和UDP协议的使用,以及ServerSocket和Socket类的API。 7. **反射与注解**:反射是Java动态性的一个重要体现,书中详细讲解了如何...
涉及到的自定义数据结构,如简单的HashMap的实现,以及ThreadLocal原理的分析也是这一部分的重点。 3、进程和线程 在这一部分中,详细讨论了进程和线程的概念,包括它们之间的区别以及并行和并发的区别。探讨了多种...
此外,还会介绍线程常用的工具类,如CountDownLatch、CyclicBarrier、Semaphore,以及Atomic原子类和ThreadLocal的作用与原理。这些内容涵盖了Java多线程编程的核心知识,对于理解和应用多线程编程至关重要。
3. **异常处理**:介绍Java中的异常处理机制,学习如何使用try-catch-finally语句块进行错误捕获和处理,理解不同类型的异常类,并学习如何创建自定义异常。 4. **集合框架**:详述ArrayList、LinkedList、HashSet...