ThreadLocal变量

版权声明:本文为博主原创文章,转载请加上原文链接,谢谢! https://blog.csdn.net/sxdtzhaoxinguo/article/details/80347081
早在 JDK 1.2 的版本中就提供 java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal 很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个 Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable 更容易让人理解一些。当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就是线程的本地变量,这也是类名中“Local”所要表达的意思。线程局部变量并不是 Java 的新发明,很多语言(如 IBM XL FORTRAN)在语法层面就提供线程局部变量。在 Java 中没有提供语言级支持,而是变相地通过 ThreadLocal 的类提供支持。
JDK 5 以后提供了泛型支持,ThreadLocal 被定义为支持泛型:
public class ThreadLocal<T> extends Object
T 为线程局部变量的类型。该类定义了 4 个方法:
1) protected T initialValue():返回此线程局部变量的当前线程的“初始值”。线程第一次使用 get() 方法访问变量时将调用此方法,但如果线程之前调用了 set(T) 方法,则不会对该线程再调用 initialValue 方法。通常,此方法对每个线程最多调用一次,但如果在调用 get() 后又调用了 remove(),则可能再次调用此方法。
该实现返回 null;如果程序员希望线程局部变量具有 null 以外的值,则必须为ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。
2)public T get():返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值
3)public void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠 initialValue() 方法来设置线程局部变量的值。
4)public void remove():移除此线程局部变量当前线程的值。如果此线程局部变量随后被当前线程读取,且这期间当前线程没有设置其值,则将调用其 initialValue() 方法重新初始化其值。这将导致在当前线程多次调用 initialValue 方法。

下面是一个使用 ThreadLocal 的例子,每个线程产生自己独立的序列号。就是使用ThreadLocal 存储每个线程独立的序列号复本,线程之间互不干扰。

package cn.micai.concurrent;

/**
 * 描述:
 * <p>
 *
 * @author: 赵新国
 * @date: 2018/5/14 9:29
 */
public class SequenceNumber {

    /**
     * 定义匿名子类创建ThreadLocal的变量
     */
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    /**
     * 下一个序列号
     * @return
     */
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }

    private static class TestClient extends Thread {

        private SequenceNumber sn;

        public TestClient(SequenceNumber sn) {
            this.sn = sn;
        }

        /**
         * 线程产生序列号
         */
        @Override
        public void run() {
            for (int i=0; i<3; i++) {
                System.out.println("thread[" + Thread.currentThread().getName() + "] " +
                        "sn[" + sn.getNextNum() + "]");
            }
        }
    }

    public static void main(String [] args) {
        SequenceNumber sn = new SequenceNumber();
        TestClient t1 = new TestClient(sn);
        TestClient t2 = new TestClient(sn);
        TestClient t3 = new TestClient(sn);
        t1.start();
        t2.start();
        t3.start();
    }

}
运行结果:

从运行结果可以看出,使用了 ThreadLocal 后,每个线程产生了独立的序列号,没有相互干扰。通常我们通过匿名内部类的方式定义 ThreadLocal 的子类,提供初始的变量值。

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而 ThreadLocal 则从另一个角度来解决多线程的并发访问。ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
需要注意的是 ThreadLocal 对象是一个本质上存在风险的工具,应该在完全理解将要使用的线程模型之后,再去使用 ThreadLocal 对象。这就引出了线程池(thread pooling)的问题,线程池是一种线程重用技术,有了线程池就不必为每个任务创建新的线程,一个线程可能会多次使用,用于这种环境的任何 ThreadLocal 对象包含的都是最后使用该线程的代码所设置的状态,而不是在开始执行新线程时所具有的未被初始化的状态。

那么 ThreadLocal 是如何实现为每个线程保存独立的变量的副本的呢?通过查看它的源代码,我们会发现,是通过把当前“线程对象”当作键,变量作为值存储在一个 Map 中。




threadlocal报错

08-08

package com.yinbo.tbms.jljiao;rnrnimport org.hibernate.HibernateException;rnimport org.hibernate.Session;rnimport org.hibernate.cfg.Configuration;rnpublic class HibernateSessionFactory rnprivate static String CONFIG_FILE_LOCATION="/hibernate.cfg.xml";rnprivate static final ThreadLocalthredLocal=new ThreadLocal();rnprivate static Configuration configuration=new Configuration();rnprivate static org.hibernate.SessionFactory sessionFactory;rnprivate static String configFile=CONFIG_FILE_LOCATION;rnstaticrn tryrn configuration.configure(configFile);rn sessionFactory=configuration.buildSessionFactory();rn catch(Exception e)rn System.err.println("%%%% Error Creating SessionFactory %%%%");rn e.printStackTrace();rn rnrnprivate HibernateSessionFactory()rn rnrnpublic static Session getSession() throws HibernateExceptionrnrn Session session=(Session)threadLocal.get();rn if(session==null||!session.isOpen())rn rn rebuildSessionFactory();rn rn session=(sessionFactory!=null)?sessionFactory.openSession():null;rn rn threadLocal.set(session);rn return session;rn rnpublic static void rebuildSessionFactory()rn rn tryrn configuration.configure(configFile);rn sessionFactory=configuration.buildSessionFactory();rn catch(Exception e)rn System.err.println("%%%% Error Creating SessionFactory %%%%");rn e.printStackTrace();rn rn rnpublic static void closeSession() throws HibernateExceptionrn rn Session session=(Session)threadLocal.get();rn threadLocal.set(null);rn if(session!=null)rn rn session.close();rn rnrnpublic static org.hibernate.SessionFactory getSessionFactory()rn return sessionFactory;rnrnpublic static void setConfigFile(String conFile)rn HibernateSessionFactory.configFile=configFile;rn sessionFactory=null;rnrnpublic static Configuration getConfiguration()rn return configuration;rnrnrn

ThreadLocal是什么

08-07

rnhttp://www.goceanedu.com.cn/Read_1863.htmrnrnrn早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。rnrnThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。rnrn当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。rnrn从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。rnrn线程局部变量并不是Java的新发明,很多语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供在语言级支持,而是变相地通过ThreadLocal的类提供支持。rnrn所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。rnrnThreadLocal的接口方法rnrnThreadLocal类接口很简单,只有4个方法,我们先来了解一下:rnrnvoid set(Object value) rn设置当前线程的线程局部变量的值。rnrnpublic Object get() rn该方法返回当前线程所对应的线程局部变量。rnrnpublic void remove() rn将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。rnrnprotected Object initialValue() rn返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。rnrn值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。rnrnThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:rnrn代码清单1 SimpleThreadLocalrnrnpublic class SimpleThreadLocal rnrnprivate Map valueMap = Collections.synchronizedMap(new HashMap());rnrnpublic void set(Object newValue) rnrnvalueMap.put(Thread.currentThread(), newValue);①键为线程对象,值为本线程的变量副本rnrnrnrnpublic Object get() rnrnThread currentThread = Thread.currentThread();rnrnObject o = valueMap.get(currentThread);②返回本线程对应的变量rnrnif (o == null && !valueMap.containsKey(currentThread)) ③如果在Map中不存在,放到Maprnrn中保存起来。rnrno = initialValue();rnrnvalueMap.put(currentThread, o);rnrnrnrnreturn o;rnrnrnrnpublic void remove() rnrnvalueMap.remove(Thread.currentThread());rnrnrnrnpublic Object initialValue() rnrnreturn null;rnrnrnrnrnrn虽然代码清单9?3这个ThreadLocal实现版本显得比较幼稚,但它和JDK所提供的ThreadLocal类在实现思路上是相近的。rnrn一个TheadLocal实例rnrn下面,我们通过一个具体的实例了解一下ThreadLocal的具体使用方法。rnrn代码清单2 SequenceNumberrnrnpackage com.baobaotao.basic;rnrnpublic class SequenceNumber rnrn①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值rnrnprivate static ThreadLocal seqNum = new ThreadLocal()rnrnpublic Integer initialValue()rnrnreturn 0;rnrnrnrn;rnrn②获取下一个序列值rnrnpublic int getNextNum()rnrnseqNum.set(seqNum.get()+1);rnrnreturn seqNum.get();rnrnrnrnpublic static void main(String[] args) rnrnrnrnSequenceNumber sn = new SequenceNumber();rnrn③ 3个线程共享sn,各自产生序列号rnrnTestClient t1 = new TestClient(sn);rnrnTestClient t2 = new TestClient(sn);rnrnTestClient t3 = new TestClient(sn);rnrnt1.start();rnrnt2.start();rnrnt3.start();rnrnrnrnprivate static class TestClient extends Threadrnrnrnrnprivate SequenceNumber sn;rnrnpublic TestClient(SequenceNumber sn) rnrnthis.sn = sn;rnrnrnrnpublic void run()rnrnrnrnfor (int i = 0; i < 3; i++) ④每个线程打出3个序列值rnrnSystem.out.println("thread["+Thread.currentThread().getName()+rnrn"] sn["+sn.getNextNum()+"]");rnrnrnrnrnrnrnrnrnrn rn通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:rnrnthread[Thread-2] sn[1]rnrnthread[Thread-0] sn[1]rnrnthread[Thread-1] sn[1]rnrnthread[Thread-2] sn[2]rnrnthread[Thread-0] sn[2]rnrnthread[Thread-1] sn[2]rnrnthread[Thread-2] sn[3]rnrnthread[Thread-0] sn[3]rnrnthread[Thread-1] sn[3]rnrn考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。rnrnThread同步机制的比较rnrnThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。rnrn在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。rnrn而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。rnrn由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal版本。rnrn概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。rnrnSpring使用ThreadLocal解决线程安全问题rnrn我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。rnrn一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9?2所示:rnrnrnrn图1同一线程贯通三层rnrn这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。rnrn下面的实例能够体现Spring对有状态Bean的改造思路:rnrn代码清单3 TopicDao:非线程安全rnrnpublic class TopicDao rnrnprivate Connection conn;①一个非线程安全的变量rnrnpublic void addTopic()rnrnStatement stat = conn.createStatement();②引用非线程安全变量rnrn…rnrnrnrnrnrn由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:rnrn代码清单4 TopicDao:线程安全rnrnimport java.sql.Connection;rnrnimport java.sql.Statement;rnrnpublic class TopicDao rnrn①使用ThreadLocal保存Connection变量rnrnprivate static ThreadLocal connThreadLocal = new ThreadLocal();rnrnpublic static Connection getConnection()rnrn②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,rnrn并将其保存到线程本地变量中。rnrnif (connThreadLocal.get() == null) rnrnConnection conn = ConnectionManager.getConnection();rnrnconnThreadLocal.set(conn);rnrnreturn conn;rnrnelsernrnreturn connThreadLocal.get();③直接返回线程本地变量rnrnrnrnrnrnpublic void addTopic() rnrn④从ThreadLocal中获取线程对应的ConnectionrnrnStatement stat = getConnection().createStatement();rnrnrnrnrnrn不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。rnrn当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。rnrn小结rnrnThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

ThreadLocal的疑问?

04-02

贴上代码:rn[code=java]rnclass Accountrnrn /* 定义一个ThreadLocal类型的变量,该变量将是一个线程局部变量rn 每个线程都会保留该变量的一个副本 */rn private ThreadLocal name = new ThreadLocal();rn // 定义一个初始化name属性的构造器rn public Account(String str)rn rn this.name.set(str);rn // 下面代码用于访问当前线程的name副本的值rn System.out.println("---" + this.name.get());rn rn // name的setter和getter方法rn public String getName()rn rn return name.get();rn rn public void setName(String str)rn rn this.name.set(str);rn rnrnclass MyTest extends Threadrnrn // 定义一个Account属性rn private Account account;rn public MyTest(Account account, String name)rn rn super(name);rn this.account = account;rn rn public void run()rn rn // 循环10次rn for (int i = 0 ; i < 10 ; i++)rn rn // 当i == 6时输出将账户名替换成当前线程名rn if (i == 6)rn rn account.setName(getName());rn rn // 输出同一个账户的账户名和循环变量rn System.out.println(account.getName()rn + " 账户的i值:" + i);rn rn rnrnpublic class ThreadLocalTestrnrn public static void main(String[] args) rn rn // 启动两条线程,两条线程共享同一个Accountrn Account at = new Account("初始名");rn /*rn 虽然两条线程共享同一个账户,即只有一个账户名rn 但由于账户名是ThreadLocal类型的,所以每条线程rn 都完全拥有各自的账户名副本,所以从i == 6之后,将看到两条rn 线程访问同一个账户时看到不同的账户名。rn */rn new MyTest(at , "线程甲").start();rn new MyTest(at , "线程乙").start ();rn rnrn[/code]rn运行后控制台输出:rn[img=http://img.bbs.csdn.net/upload/201404/02/1396373975_752643.jpg][/img]rn我的困惑是:为什么在i变为6之前,account.getName()的输出为null?主线程启动的两个线程的构造器里都传入了at,那么,这个account.getName()为什么不输出为“初始名”呢,不是副本吗,副本应该和本体一样才对啊?求高人讲解。。。

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试