`
该用户名已经存在
  • 浏览: 308564 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

另一个角度理解java的ThreadLocal

    博客分类:
  • Java
阅读更多
关于Java的ThreadLocal网上有大量的文章在谈这个问题,为什么这个东西大家说来说去乐此不疲呢,大约是因为它本身的神秘性,经常出现在一些框架中,但是自己又很少用。亦或是因为大家本身对它的理解各有偏颇,所以成了大家热议的话题。
我对ThreadLocal的理解也不深,这篇文章也不会去做什么深层次的研究,对源码分析的文章也有很多写的非常好的,我只是想从另外一个角度去认识ThreadLocal。

ThreadLocal被翻译成“线程本地变量”,从名字上讲首先它是“变量”,那么,我们就来看看Java中的各种变量。
一、局部变量
定义:方法中,代码块中,方法参数中定义的变量。
作用域:方法内部,代码块内部。其他方法,其他代码块不能访问。
生命周期:方法或者代码块调用开始到方法或者代码块调用结束。
共享性:方法内部,代码块内部共享,对于多个线程来讲,变量初始化到自己的工作内存中,主内存不存在该变量,所以线程之间不共享。
例子:
public void method1() {
        //方法执行到此处时变量a才被创建。
        int a=1;
        for (int i=0; i<10; i++) {
            System.out.println(i);
        }
        if (true) {
            int i= 2;
        }
        
        {
            int i=3;
        }
    }
    
public void method2() {
    int a=2;
}

同一个类中的method1和method2中都可以定义一个名为a的变量,同样method1中的for代码块,if代码块和{}代码块中都可以定义名为i的变量。它们的作用域都在方法内部或者代码块内部。

二、成员变量
定义:成员变量又称为成员属性,它是描述对象状态的数据,是类中很重要的组成成分。
作用域:整个类实例内部。
生命周期:伴随整个类实例始终,变量在创建类实例时被创建。
共享性(同一个类实例):在整个类实例共享,对于多线程来讲,变量被初始化到主内存中,每个线程拷贝变量到工作内存中进行操作,线程之间共享一个主内存变量,存在线程安全问题。
例子:
public class Clazz {
    private int a;
    
    public void method() {
        a ++;
    }
}

对于同一个Clazz类的实例来讲,变量a的作用域存在于整个Clazz实例,如果多线程之间共享一个Clazz实例,变量a是存在线程安全问题的。

三、全局变量
定义:全局变量在Java中也可以叫静态变量,通过static关键字修饰。
作用域:整个类。
生命周期:伴随整个类始终,变量在第一次使用该类时被创建。
共享性:在整个类共享,对于多线程来讲,变量被初始化到主内存中,每个线程拷贝变量到工作内存中进行操作,线程之间共享一个主内存变量,存在线程安全问题。
public class Clazz {
    public static int a = 0;
}

说了这么多,那么跟ThreadLocal有什么关系呢?
思考为什么要定义这么多的变量类型呢?
局部变量解决了方法内部,代码块内部行之间的变量传递问题。如果没有局部变量,不知道行之间怎么传递变量。
成员变量解决了类实例各方法之间的变量传递。如果没有成员变量,方法之间变量传递只能靠参数。
全局变量解决了类之间的变量传递。如果没有全局变量,类之间变量只能靠构造实例的时候相互传递。

那么ThreadLocal也是变量,该变量解决了什么问题呢?
ThreadLocal解决了变量在同一个线程内部之间的传递。
ThreadLocal首先不是解决线程安全问题,最显而易见的原因是ThreadLocal内部保存的变量在多线程之间不共享。数据都不共享,谈何线程安全?当然了,如果ThreadLocal内部保存的变量本身就是一个多线程共享的数据,那么还是会有线程安全的问题的。如果没有ThreadLocal,我们需要在同一个线程之内共享的数据大约只能通过方法传递了。这样可能会让代码显得杂乱。

四、ThreadLocal变量
定义:线程本地变量。
作用域:线程内部。
生命周期:伴随线程执行始终,线程结束,变量生命结束。
共享性:多个线程之间不共享。






10
8
分享到:
评论
9 楼 liangcoder 2014-03-11  
博主总结的相当到位!从作用域 生命周期 共享性的纬度比较了Java中常见的变量类型,这是一个种很不错的方式,同理可适用于volatile类型的变量。

看了下评论,关于ThreadLocal变量对于线程安全的影响,同意楼主的观点 ”ThreadLocal的变量不存在线程安全的问题“,额外补充一点: ThreadLocal变量可以解决线程不安全的问题。

举例说明:
对于线程不安全的SimpleDataFormat对象,如果在线程池中使用并声明为ThreadLocal类型,可以有效的减少其对象数量,同事保持线程安全。
8 楼 该用户名已经存在 2014-03-11  
kmkim 写道
“如果ThreadLocal内部保存的变量本身就是一个多线程共享的数据,那么还是会有线程安全的问题的。”


请问LZ说的这句话怎么理解?(我理解的ThreadLocal是每个线程自己保存一个副本到自己的工作内存,然后不同步到主内存。这样ThreadLocal内部保存的变量本身就不会是一个多线程共享的数据了,不知道我理解的是不是有误,麻烦指正。)


对。我的表述有误。
“如果ThreadLocal内部保存的变量本身就是一个多线程共享的数据,那么还是会有线程安全的问题的。”这句话说的不恰当。
正如你所说,无论ThreadLocal里面存放的是怎样的数据,多线程环境下你从当前线程的ThreadLocalMap中取出来的值确实是线程安全的,因为其他线程永远无法修改。
但是对于这个共享对象本身来讲,它的值是会被修改的。也不会多线程安全。
举个例子吧。

public class JavaVariable {
    //多线程共享值
    public static int a = 0;
    
    private static void plus1() throws Exception {
		//主线程将共享值修改成 10
		a = 10;
		for (int i = 0; i < 10; i++) {
			new Thread() {
				public void run() {
					//每个线程都将初始值0赋值给共享值a=0
					a = threadLocal.get();
					//修改a=1
					a++;
					//将修改后的a放到自己的ThreadLocalMap中,此后,对于该线程来说,获取到的ThreadLocalMap中的值永远是放进去的a=1,不会被其他线程修改。
					threadLocal.set(a);
					System.out.println("plus1:" + Thread.currentThread().getName() + ": " + threadLocal.get());
				}
			}.start();
		}
		Thread.sleep(1000);
		//但是主线程的a被其他线程修改了。输出不是10,而是1
		System.out.println(a);
	}
}

不知道这个例子恰到不恰当。其实这个大家一直难理解的就是ThreadLocal保存的值对于当前的线程来讲是线程安全的。我想说的是这个线程安全没什么关系。。因为一个值一旦被保存到ThreadLocal中时,他就不再和其他线程共享了,数据都不共享,怎么扯得上线程安不安全呢?

上面的例子就是每个线程修改共享的值。一般正常人用ThreadLocal都是这样的:
....
Object obj = threadLocal.get();
if (null == obj) {
    // 每个线程创建一个自己的Object,不和任何线程共享。
    obj = new Object();
    threadLocal.set(obj);
}

说ThreadLocal是线程安全的当然没错,但是说这样的话就好比说:你是看不见鬼的,废话,因为压根就没有鬼。

ThreadLocal不是为了解决线程安全的问题而设计的,是为了解决同一个线程内变量共享而设计的。
7 楼 该用户名已经存在 2014-03-11  
hunnuxiaobo 写道
lianglaiyang 写道
总结的非常好!
但我还想过一个问题,一直没有找个合适的答案,望不吝赐教:
我们tomcat是用线程池的,一个线程会被重复利用,那threadlocal会不会出现混乱的情况呢?

线程被重复利用,ThreadLocal也会重复利用,所以应该设置一个Filter,在请求结束后清理ThreadLocal,据我所知,Struts2里面就是这么做的。


对,因为ThreadLocalMap是Thread的一个引用存在的,只要Thread是一个,ThreadLocalMap就是一个,会重复利用。
一般情况下,我们自己用的话ThreadLocal里面都存放的是无状态的对象,只是便于在同一个线程中参数传递,所以个人认为即使重复利用也没有关系。
但是如果ThreadLocal里面存放的是有状态的对象的话,比如 Struts2里面的ActionContext。
public class ActionContext implements Serializable {
    static ThreadLocal actionContext = new ThreadLocal();
    ....
    public static void setContext(ActionContext context) {
        actionContext.set(context);
    }
}

在struts2的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter类中,就会无论如何在请求结束后清除当前线程的ThreadLocalMap中的值:
} finally {
            prepare.cleanupRequest(request);
        }

public void cleanupRequest(HttpServletRequest request) {
    ActionContext.setContext(null);
}

6 楼 kmkim 2014-03-10  
“如果ThreadLocal内部保存的变量本身就是一个多线程共享的数据,那么还是会有线程安全的问题的。”


请问LZ说的这句话怎么理解?(我理解的ThreadLocal是每个线程自己保存一个副本到自己的工作内存,然后不同步到主内存。这样ThreadLocal内部保存的变量本身就不会是一个多线程共享的数据了,不知道我理解的是不是有误,麻烦指正。)
5 楼 timer_yin 2014-03-10  
感谢分享!!
4 楼 hunnuxiaobo 2014-03-10  
lianglaiyang 写道
总结的非常好!
但我还想过一个问题,一直没有找个合适的答案,望不吝赐教:
我们tomcat是用线程池的,一个线程会被重复利用,那threadlocal会不会出现混乱的情况呢?

线程被重复利用,ThreadLocal也会重复利用,所以应该设置一个Filter,在请求结束后清理ThreadLocal,据我所知,Struts2里面就是这么做的。
3 楼 lianglaiyang 2014-03-10  
总结的非常好!
但我还想过一个问题,一直没有找个合适的答案,望不吝赐教:
我们tomcat是用线程池的,一个线程会被重复利用,那threadlocal会不会出现混乱的情况呢?
2 楼 793059909 2014-03-09  
1 楼 xugangqiang 2014-03-09  
楼主写的非常好
这解决了我的一个困惑
思路非常好

相关推荐

    java后端调用大华视频的demo

    "java后端调用大华视频的demo"就是一个典型的示例,它展示了如何利用Java调用大华提供的SDK来访问并控制大华监控设备。在这个场景中,开发者通常需要具备以下关键知识点: 1. **Java Native Interface (JNI)**:...

    java学习课件

    通过深入学习这些知识点,一个初学者可以逐步成长为熟练的Java开发者,具备解决实际问题的能力。三套课件的设置意味着学习者可以从不同角度和深度来探索Java的世界,确保全面理解和掌握这一强大的编程语言。

    java面试常见问题总结

    在Java面试中,死锁是一个非常重要的概念,面试官可能会从不同的角度来考察候选人对于死锁的理解程度。以下是对死锁相关知识点的详细介绍: - **产生死锁的原因:** 1. **系统资源不足**:当系统中的可用资源数量...

    Java并发编程原理与实战

    从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的原理(理论层面).mp4 synchronized保证线程安全的原理(jvm层面).mp4 单例问题与线程安全性深入解析.mp4 理解自旋锁,死锁与重入锁.mp4 深入...

    Java架构师面试题

    Java架构师面试题涵盖了许多关键领域,包括J2EE开发、大数据处理、日志管理、权限分配、服务扩展性、负载均衡、性能调优、系统整合、软件开发模型、云计算理解以及框架比较与安全性分析。以下是对这些知识点的详细...

    阿里Java并发程序设计教程

    在并发程序设计中,我们需要控制多个线程的执行流程,CountDownLatch是一种同步辅助类,允许一个或多个线程等待其他线程完成操作。CyclicBarrier是一种用于多线程协作的同步工具,允许一组线程相互等待直到所有线程...

    龙果java并发编程完整视频

    第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节synchronized保证线程安全的原理(理论层面)00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题与...

    龙果 java并发编程原理实战

    第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节synchronized保证线程安全的原理(理论层面) 00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题...

    举例解析Java多线程编程中需要注意的一些关键点

    ThreadLocal是Java提供的一种线程局部变量,它为每个线程创建了一个独立的变量副本。这意味着每个线程都拥有自己的一份变量,互不影响。ThreadLocal常用于在多线程环境中为每个线程维护独立的状态,例如,记录线程的...

    Java 并发编程原理与实战视频

    第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节synchronized保证线程安全的原理(理论层面) 00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题...

    java并发编程

    第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节sy nchronized保证线程安全的原理(理论层面)00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题...

    并发编程代码

    并发编程是计算机科学中的一个重要领域,它涉及到如何在多处理器或多线程环境下有效地执行程序,以提高系统的整体性能。在现代计算机系统中,由于多核处理器的普及,并发编程成为了编写高效软件的关键技术。以下是对...

    Java互联网架构多线程并发编程原理及实战 视频教程 下载.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....

    Java互联网架构多线程并发编程原理及实战 视频教程 下载4.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....

    Java互联网架构多线程并发编程原理及实战 视频教程 下载2.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....

    Java互联网架构多线程并发编程原理及实战 视频教程 下载3.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....

    Java互联网架构多线程并发编程原理及实战 视频教程 下载1.zip

    Java互联网架构多线程并发编程原理及实战 视频教程 下载 1-1 课程简介.mp4 1-2 什么是并发编程.mp4 1-3 并发编程的挑战之频繁的上下文切换.mp4 1-4 并发编程的挑战之死锁.mp4 1-5 并发编程的挑战之线程安全....

Global site tag (gtag.js) - Google Analytics