ThreadLocal类的实现用法

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

ThreadLocal的接口方法

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。

模拟实现ThreadLocal代码:

 1 public class ThreadShareData {
 2     static int num = 0;
 3
 4
 5     public static void main(String[] args) {
 6         Map<Thread, Integer> map = new HashMap<Thread, Integer>();
 7         for (int i = 0; i < 2; i++) {
 8             new Thread(new Runnable() {
 9                 @Override
10                 public void run() {
11                     int num = new Random().nextInt();
12                     System.out.println(Thread.currentThread().getName()+":"+" get num: "+num);
13                     new A().get();
14                     new B().get();
15                 }
16             }).start();
17         }
18     }
19
20     static class A{
21         public void get(){
22             System.out.println("A: "+Thread.currentThread().getName() +"get num: "+num);
23         }
24     }
25
26     static class B{
27         public void get(){
28             System.out.println("A: "+Thread.currentThread().getName() +"get num: "+num);
29         }
30     }
31
32 }

ThreadLocal的经典用法:

 1 public class ThreadLocalShareData2 {
 2     static ThreadLocal<People> threadLocal = new ThreadLocal<People>();
 3     public static void main(String[] args) {
 4         for (int i = 0; i < 2; i++) {
 5             new Thread(new Runnable() {
 6                 @Override
 7                 public void run() {
 8                     int data = new Random().nextInt();
 9                     People people = new People().getInstance();
10                     people.setName("name"+data);
11                     people.setAge(data);
12                     System.out.println(Thread.currentThread().getName()+" set name "+people.getName()+" set age "+people.getAge());
13                     new A().get();
14                     new B().get();
15                 }
16             }).start();
17         }
18     }
19
20     static class A{
21         public void get(){
22             System.out.println("A: "+Thread.currentThread().getName() +"get name "+new People().getInstance().getName()+" get age "+new People().getInstance().getAge());
23         }
24     }
25     static class B{
26         public void get(){
27             System.out.println("B: "+Thread.currentThread().getName() +"get name "+new People().getInstance().getName()+" get age "+new People().getInstance().getAge());
28         }
29     }
30     static class People{
31         private People(){
32
33         }
34         public People getInstance(){
35             People people = threadLocal.get();
36             if(people == null){
37                 people = new People();
38                 threadLocal.set(people);
39             }
40             return people;
41         }
42         private int age;
43         private String name;
44         public int getAge() {
45             return age;
46         }
47         public String getName() {
48             return name;
49         }
50         public void setAge(int age) {
51             this.age = age;
52         }
53         public void setName(String name) {
54             this.name = name;
55         }
56     }
57 }

将ThreadLocal仿单例模式进行实现,更加面向对象。

在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

 1 public class TestNum {
 2     // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
 3     private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
 4         public Integer initialValue() {
 5             return 0;
 6         }
 7     };
 8
 9     // ②获取下一个序列值
10     public int getNextNum() {
11         seqNum.set(seqNum.get() + 1);
12         return seqNum.get();
13     }
14
15     public static void main(String[] args) {
16         TestNum sn = new TestNum();
17         // ③ 3个线程共享sn,各自产生序列号
18         TestClient t1 = new TestClient(sn);
19         TestClient t2 = new TestClient(sn);
20         TestClient t3 = new TestClient(sn);
21         t1.start();
22         t2.start();
23         t3.start();
24     }
25
26     private static class TestClient extends Thread {
27         private TestNum sn;
28
29         public TestClient(TestNum sn) {
30             this.sn = sn;
31         }
32
33         public void run() {
34             for (int i = 0; i < 3; i++) {
35                 // ④每个线程打出3个序列值
36                 System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["
37                          + sn.getNextNum() + "]");
38             }
39         }
40     }
41 }  

我们发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

Thread同步机制的比较:

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。

  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

  Spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9?2所示:

  同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

  下面的实例能够体现Spring对有状态Bean的改造思路:

  非线程安全代码:

  

1 public class TestDao {
2     private Connection conn;// ①一个非线程安全的变量
3
4     public void addTopic() throws SQLException {
5         Statement stat = conn.createStatement();// ②引用非线程安全变量
6         // …
7     }
8 }  

  线程安全代码:

 1 public class TestDaoNew {
 2     // ①使用ThreadLocal保存Connection变量
 3     private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
 4
 5     public static Connection getConnection() {
 6         // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
 7         // 并将其保存到线程本地变量中。
 8         if (connThreadLocal.get() == null) {
 9             Connection conn = getConnection();
10             connThreadLocal.set(conn);
11             return conn;
12         } else {
13             return connThreadLocal.get();// ③直接返回线程本地变量
14         }
15     }
16
17     public void addTopic() throws SQLException {
18         // ④从ThreadLocal中获取线程对应的Connection
19         Statement stat = getConnection().createStatement();
20     }
21 }  

不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

  当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。

 1 public class ConnectionManager {
 2
 3     private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
 4         @Override
 5         protected Connection initialValue() {
 6             Connection conn = null;
 7             try {
 8                 conn = DriverManager.getConnection(
 9                         "jdbc:mysql://localhost:3306/test", "username",
10                         "password");
11             } catch (SQLException e) {
12                 e.printStackTrace();
13             }
14             return conn;
15         }
16     };
17
18     public static Connection getConnection() {
19         return connectionHolder.get();
20     }
21
22     public static void setConnection(Connection conn) {
23         connectionHolder.set(conn);
24     }
25 }  

还有一个经典实例,在HibernateUtil中,用于session的管理:

 1 public class HibernateUtil {
 2     private static Log log = LogFactory.getLog(HibernateUtil.class);
 3     private static final SessionFactory sessionFactory;     //定义SessionFactory
 4
 5     static {
 6         try {
 7             // 通过默认配置文件hibernate.cfg.xml创建SessionFactory
 8             sessionFactory = new Configuration().configure().buildSessionFactory();
 9         } catch (Throwable ex) {
10             log.error("初始化SessionFactory失败!", ex);
11             throw new ExceptionInInitializerError(ex);
12         }
13     }
14
15     //创建线程局部变量session,用来保存Hibernate的Session
16     public static final ThreadLocal session = new ThreadLocal();
17
18     /**
19      * 获取当前线程中的Session
20      * @return Session
21      * @throws HibernateException
22      */
23     public static Session currentSession() throws HibernateException {
24         Session s = (Session) session.get();
25         // 如果Session还没有打开,则新开一个Session
26         if (s == null) {
27             s = sessionFactory.openSession();
28             session.set(s);         //将新开的Session保存到线程局部变量中
29         }
30         return s;
31     }
32
33     public static void closeSession() throws HibernateException {
34         //获取线程局部变量,并强制转换为Session类型
35         Session s = (Session) session.get();
36         session.set(null);
37         if (s != null)
38             s.close();
39     }
40 }

总结:

  ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

  ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

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

  Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

  当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。

参考文档:

JDK 官方文档

http://blog.csdn.net/lufeng20/article/details/24314381

http://lavasoft.blog.51cto.com/62575/51926/

时间: 2024-10-13 20:59:41

ThreadLocal类的实现用法的相关文章

java的ThreadLocal类的用法

java的ThreadLocal类的用法,ThreadLocal是一个支持泛型的类,用在多线程中用于防止并发冲突问题. 例如下面的一个例子,就是用于线程增加1,但是相互不冲突 package com.test.threadlocal; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Local { private static ThreadLocal<Integer

ThreadLocal类的用法与源码解析

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各个线程中访问的是不同的对象. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.通过ThreadLocal.set()将这个新创建

多线程06_张孝祥-ThreadLocal类及应用技巧

使用ThreadLocal类实现线程范围内的共享数据: 确保每个线程都有自己私有的变量和取到自己的变量.下面用ThreadLocal类实现,替代上节课中的map用法 代码: package org.yla.thread; import java.util.Random; /** * 使用ThreadLocal类实现线程范围内的私有数据 * 替代上节课map的作用 * @author huo_chai_gun * 2014年12月18日下午3:25:35 */ public class Threa

【ThreadLocal】深入JDK源码之ThreadLocal类

学习JDK中的类,首先看下JDK API对此类的描述,描述如下: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本.ThreadLocal其实就是一个工具类,用来操作线程局部变量,ThreadLocal 实例通常是类中的 private static 字段.它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联. 例如,以下类生成对每个线程

弄明白ThreadLocal类

1.ThreadLocal类的由来 因为有问题,人类就会想法设法的创造一些东西出来解决问题,嗯,这句话同意吧. 假如目前有这么一个问题:有个家庭,三个孩子都想看妈妈买的一本童话书,但是只有一本书,该如何是好? 方法一:家里没钱买第二本了,那就排队看,谁跑得快来到妈妈面前的就先看.后面来晚的,候着等着.等前面的看完再到你.于是 以时间换空间的synchronized 类出现了. 方法二:多大的事儿,你们爸爸有钱,随便任性.立马再买两本一模一样的,人手一本.于是以空间换时间的ThreadLocal类

ThreadLocal类详解

众所周知,ThreadLocal对象可以每一个线程保存一份值,可以避免因线程间共享数据带来的问题. 其实现的原理,大致如下,具体的可以参考JDK里的源码. Thread类中,有一个threadLocals字段,它是ThreadLocalMap类型(ThreadLocal里的一个静态内部类).该字段存放当前线程下,所有与ThreadLocal相关的值.该对象是一个Map,key为ThreadLocal对象,value为所存放的值. 在ThreadLocal类里,有两个重要的方法:set()和get

用ThreadLocal类实现线程安全的正确姿势

大家通常知道,ThreadLocal类可以帮助我们实现线程的安全性,这个类能使线程中的某个值与保存值的对象关联起来.ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值.从概念上看,我们把ThreadLocal<T>理解成一个包含了Map<Thread,T>的对象,其中Map的key用来标识不同的线程,而Map的value存放了特定该线程的某个值.但是ThreadL

2015年11月26日 Java基础系列(三)ThreadLocal类初级学习

序,ThreadLocal类是为了解决多线程的安全问题.线程安全的意思也就是说每个线程操作自己的变量,不要对其他线程的值造成影响. 在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性. 1 ThreadLocal解决多线程安全问题的思路是通过ThreadLocalMap进行管控,就像一个Map中存在多个键值对,ThreadLocalMap可以对比Map进行理解. 2 一般通过覆盖ThreadLocal的init

浅谈对Java中ThreadLocal类的理解

首先要明确:ThreadLocal不是一个多线程类,或者应该叫做线程局部变量.这从ThreadLocal的JDK定义中就可以看到 public class ThreadLocal<T>extends Object 可以看出ThreadLocal只是一个普普通通的类,并没有继承自Thread或实现Runnable接口. 同时也可以看到ThreadLocal使用了泛型,这样他就可以操作几乎任何类型的数据了.下面说JDK API代码时具体再说. 对此类,看看JDK API中的部分描述: 该类提供了线