ThreadLocal作用

转载背景:

1. 小白从2014.10.31~2014.11.3一直纠结在一处bug,今天在大神的成功案例对比了一下,严肃地感叹自己的在【复制粘贴】过程中的对于原理的稀里糊涂的理解!

2. 理解重点:非线性安全的Session的在web应用中的三层(dao层,servlet层,service层)中,以ThreadLocal存放,在同一次请求相应调用的线程中,所有关联的对象引用的都是一个对象!【但是!!小白却没有定义这样的Session,导致从service-》servlet-》dao层的本该一气呵成的代码变成了拿破仑的滑铁卢,执行了一半!】

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。 ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一 些。 


当使用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是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单: 
在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本: 

代码清单 
SimpleThreadLocal.java 
package cn.sccl.demo; 

import java.util.Collections; 
import java.util.HashMap; 
import java.util.Map; 

public class SimpleThreadLocal { 

private Map valueMap = Collections.synchronizedMap(new HashMap()); 

public void set(Object newValue) { 
   valueMap.put(Thread.currentThread(), newValue); 
   // key is the current thread object,value is the copy of the variable of 
   // the current thread 


public Object get() { 
   Thread currentThread = Thread.currentThread(); 
   Object o = valueMap.get(currentThread); 
   //return the variable of the current thread 
   if (o == null && !valueMap.containsKey(currentThread)) { 
    // if the variable is not in the map,put it in the map 
    o = initialValue(); 
    valueMap.put(currentThread, o); 
   } 
   return o; 


public void remove() { 
   valueMap.remove(Thread.currentThread()); 


public Object initialValue() { 
   return null; 





它和JDK所提供的ThreadLocal类在实现思路上是相近的。 

一个TheadLocal实例 

了解一下ThreadLocal的具体使用方法。 

代码清单 
SequenceNumber.java: 

package cn.sccl.demo; 

public class SequenceNumber { 
// overwrite the ThreadLocal method initialValue() to set a initial value 
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() { 
   public Integer initialValue() { 
    return 0; 
   } 
}; 

// get next number 
public int getNextNum() { 
   seqNum.set(seqNum.get() + 1); 
   return seqNum.get(); 


public static void main(String[] args) { 
   SequenceNumber sn = new SequenceNumber(); 
   // 3 threads use the same number variable and increase it 
   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() { 
    for (int i = 0; i < 3; i++) { 
     // every thread print 3 number 
     System.out.println("thread[" + Thread.currentThread().getName() 
       + "] sn[" + sn.getNextNum() + "]"); 
    } 
   } 



运行后控制台输出如下: 
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] 

输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过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应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程 

同一线程贯通三层 

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

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

代码清单 
TopicDao.java 非线程安全 

public class TopicDao { 
private Connection conn; 
//一个非线程安全的变量 

public void addTopic(){ 
   Statement stat = conn.createStatement(); 
   //引用非线程安全变量 



由于conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造: 

代码清单 
TopicDao.java:线程安全 
package cn.sccl.demo.tmp; 

import java.sql.Connection; 
import java.sql.SQLException; 
import java.sql.Statement; 

public class TopicDao { 

// use ThreadLocal to hold Connection variable 
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); 

public static Connection getConnection() { 
   // if connThreadLocal is empty,put a new connection in 
   if (connThreadLocal.get() == null) { 
    Connection conn = null;//ConnectionManager.getConnection(); 
    connThreadLocal.set(conn); 
    return conn; 
   } else { 
    return connThreadLocal.get(); 
    // return the thread local variable 
   } 


public void addTopic() { 
   // get the connection from the thread local 
   try { 
    Statement stat = getConnection().createStatement(); 
   } catch (SQLException e) { 
    e.printStackTrace(); 
   } 




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

当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时 不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类 使用ThreadLocal保存Connection。但这个实例基本上说明了Spring对有状态类线程安全化的解决思路。 

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下, ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

转自:http://blog.youkuaiyun.com/tianlincao/article/details/6039926

阅读伴乐:灌篮高手---好想大声叫喜欢你

内容概要:文章以“智能网页数据标注工具”为例,深入探讨了谷歌浏览器扩展在毕业设计中的实战应用。通过开发具备实体识别、情感分类等功能的浏览器扩展,学生能够融合前端开发、自然语言处理(NLP)、本地存储与模型推理等技术,实现高效的网页数据标注系统。文中详细解析了扩展的技术架构,涵盖Manifest V3配置、内容脚本与Service Worker协作、TensorFlow.js模型在浏览器端的轻量化部署与推理流程,并提供了核心代码实现,包括文本选择、标注工具栏动态生成、高亮显示及模型预测功能。同时展望了多模态标注、主动学习与边缘计算协同等未来发展方向。; 适合人群:具备前端开发基础、熟悉JavaScript和浏览器机制,有一定AI模型应用经验的计算机相关专业本科生或研究生,尤其适合将浏览器扩展与人工智能结合进行毕业设计的学生。; 使用场景及目标:①掌握浏览器扩展开发全流程,理解内容脚本、Service Worker与弹出页的通信机制;②实现在浏览器端运行轻量级AI模型(如NER、情感分析)的技术方案;③构建可用于真实场景的数据标注工具,提升标注效率并探索主动学习、协同标注等智能化功能。; 阅读建议:建议结合代码实例搭建开发环境,逐步实现标注功能并集成本地模型推理。重点关注模型轻量化、内存管理与DOM操作的稳定性,在实践中理解浏览器扩展的安全机制与性能优化策略。
基于Gin+GORM+Casbin+Vue.js的权限管理系统是一个采用前后端分离架构的企业级权限管理解决方案,专为软件工程和计算机科学专业的毕业设计项目开发。该系统基于Go语言构建后端服务,结合Vue.js前端框架,实现了完整的权限控制和管理功能,适用于各类需要精细化权限管理的应用场景。 系统后端采用Gin作为Web框架,提供高性能的HTTP服务;使用GORM作为ORM框架,简化数据库操作;集成Casbin实现灵活的权限控制模型。前端基于vue-element-admin模板开发,提供现代化的用户界面和交互体验。系统采用分层架构和模块化设计,确保代码的可维护性和可扩展性。 主要功能包括用户管理、角色管理、权限管理、菜单管理、操作日志等核心模块。用户管理模块支持用户信息的增删改查和状态管理;角色管理模块允许定义不同角色并分配相应权限;权限管理模块基于Casbin实现细粒度的访问控制;菜单管理模块动态生成前端导航菜单;操作日志模块记录系统关键操作,便于审计和追踪。 技术栈方面,后端使用Go语言开发,结合Gin、GORM、Casbin等成熟框架;前端使用Vue.js、Element UI等现代前端技术;数据库支持MySQL、PostgreSQL等主流关系型数据库;采用RESTful API设计规范,确保前后端通信的标准化。系统还应用了单例模式、工厂模式、依赖注入等设计模式,提升代码质量和可测试性。 该权限管理系统适用于企业管理系统、内部办公平台、多租户SaaS应用等需要复杂权限控制的场景。作为毕业设计项目,它提供了完整的源码和论文文档,帮助学生深入理解前后端分离架构、权限控制原理、现代Web开发技术等关键知识点。系统设计规范,代码结构清晰,注释完整,非常适合作为计算机相关专业的毕业设计参考或实际项目开发的基础框架。 资源包含完整的系统源码、数据库设计文档、部署说明和毕
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值