Java多线程之ThreadLocal怎么使用


这篇文章主要讲解了“Java多线程之ThreadLocal怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java多线程之ThreadLocal怎么使用”吧!ThreadLocal叫做线程变量,用于在多线程环境下创建线程本地变量。通俗的讲,ThreadLocal可以让你在同一个线程中创建一个变量,并且这个变量对于该线程是唯一的,其他线程无法访问到这个变量。这种方式能够有效地避免多线程之间的变量冲突问题,使得线程本地变量的访问变得更加安全和高效。例如,在一个线程池中,每个线程需要维护自己的状态,这时就可以使用ThreadLocal来创建线程本地变量来存储状态信息。在多线程编程中,由于不同线程之间共享内存,如果多个线程访问同一个变量,就会发生竞争条件,可能会导致数据不一致或者死锁等问题。使用ThreadLocal可以解决这个问题,因为它可以为每个线程创建一个独立的变量副本,每个线程都可以访问自己的变量副本,而不会影响其他线程的变量。这种方式可以有效地避免多线程之间的变量冲突问题,提高了程序的可靠性和性能。ThreadLocal常用于实现线程安全的单例模式,以及在多线程环境下对共享数据的缓存。直接上代码:创建ThreadLocal实例的方式非常简单,只需要使用Java中的ThreadLocal类的构造函数即可。上面的代码创建了一个ThreadLocal实例,该实例可以存储String类型的值。在使用ThreadLocal之前,需要先调用它的set()方法来初始化一个线程本地变量, 否则get()方法得到的值就是null。从代码中可以看到, 我们在main方法中分别创建了2个线程, 三个线程分表获取了自己线程存放的变量,他们之间变量的获取并不会错乱。如果在当前线程中尚未设置该值或者已经调用remove()方法删除值,则返回null。需要注意的是,每个ThreadLocal对象只能存储一个值,如果需要存储多个值,则需要创建多个ThreadLocal对象。ThreadLocal和Synchronized都是Java中用于处理多线程并发访问的工具,但它们的作用和实现方式有很大的区别。作用不同:ThreadLocal主要是用来创建线程本地变量,解决多线程并发访问时的变量冲突问题;而Synchronized则是一种同步机制,用于保护共享资源,防止多线程之间的竞争条件。实现方式不同:ThreadLocal通过为每个线程创建独立的变量副本,使得每个线程之间互不干扰,从而解决多线程访问共享变量时的线程安全问题。而Synchronized则是通过互斥访问来实现同步的,即多个线程同时只能有一个线程访问共享资源。应用场景不同:ThreadLocal适用于需要在多个线程中使用独立的变量的场景,如线程池中的线程状态管理,以及Web应用中的Session管理等;而Synchronized则适用于需要保护共享资源的场景,如多个线程同时访问同一个数据结构,或者需要保证某个方法在同一时刻只能被一个线程访问等。性能影响不同:ThreadLocal相对于Synchronized来说性能更好,因为它只涉及到线程本地变量的访问和赋值操作,不需要进行锁竞争和上下文切换等操作。而Synchronized则需要进行锁竞争和上下文切换等操作,会对性能产生一定的影响。ThreadLocal的优点:线程安全:每个线程都拥有自己的变量副本,不会受到其他线程的影响,可以避免线程安全问题。性能高:ThreadLocal使用了空间换时间的方式,每个线程都有自己的变量副本,不需要进行加锁和解锁操作,因此性能更高。代码简洁:使用ThreadLocal可以避免复杂的同步控制逻辑。加锁的优点:保证数据一致性:通过加锁可以保证共享资源在多线程环境下的正确性,避免出现数据不一致的情况。线程同步:在加锁过程中,线程会被阻塞,等待锁的释放,保证了线程同步。ThreadLocal的缺点:内存泄漏:ThreadLocal使用静态的内部Map来存储变量副本,如果不及时清理,会导致内存泄漏问题(后续展开介绍)。难以调试:由于每个线程都有自己的变量副本,因此在调试过程中,需要考虑多个线程的情况,会增加调试的难度。加锁的缺点:性能问题:在高并发情况下,加锁会导致线程的阻塞,从而影响系统的性能。容易导致死锁:如果加锁的操作不正确,可能会导致死锁问题,需要谨慎使用。综合来看,ThreadLocal适合处理线程私有的数据,而加锁适合处理共享的资源,具体应该根据业务需求来选择。直接查看源码:从源码中我们可以看到, 在set方法中, 我们先是获取到当前线程, 然后以当前线程为入参调用getMap方法, 并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap, 并将value值初始化。那么threadLocalMap又是什么呢? 我们接着往下看。从代码我们可以看到, threadLocalMap是ThreadLocal中的一个静态内部类, 在threadLocalMap又维护了一个名叫table的Entry数组。Entry是什么呢?Entry是一组组数据对, 而且继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。key 就是 ThreadLocal,肯定不为空,但也是弱引用的。也就是说,当 key 为 null 时,说明 ThreadLocal 已经被回收了,对应的 Entry 就应该被清除了。在ThreadLocalMap中的set方法与构造方法能看到以下代码片段。int i = key.threadLocalHashCode & (len-1)int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)简而言之就是将threadLocalHashCode进行一个位运算(取模)得到索引i,threadLocalHas免费云主机域名hCode代码如下。因为static的原因,在每次new ThreadLocal()时因为threadLocalHashCode的初始化,会使threadLocalHashCode值自增一次,增量为0x61c88647。0x61c88647是斐波那契散列乘数,它的优点是通过它散列(hash)出来的结果分布会比较均匀,可以很大程度上避免hash冲突。有兴趣可以深入研究下去, 这里就不过多赘述了, 这里这样运算就是为了避免索引下标冲突。总结一下:对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。理解了set方法,get方法也就清楚明了,直接通过计算出索引直接从数组对应位置读取即可。ThreadLocal对象的垃圾回收机制比较特殊,主要涉及到两个对象:ThreadLocal对象和ThreadLocalMap对象。每个ThreadLocal对象都会在当前线程的ThreadLocalMap中创建一个Entry对象,这个Entry对象包含了ThreadLocal对象和其对应的值。当ThreadLocal对象没有被其他对象引用,并且当前线程结束时,这个ThreadLocal对象会被标记为可回收的,并且被添加到一个特殊的ReferenceQueue中。当垃圾回收器扫描到ReferenceQueue中的ThreadLocal对象时,它会将ThreadLocal对象对应的Entry对象从ThreadLocalMap中删除,并且清除Entry对象中对ThreadLocal对象和值的引用,从而使得ThreadLocal对象和值都能够被回收。需要注意的是,虽然ThreadLocal对象被回收了,但是它在ThreadLocalMap中对应的Entry对象并没有被立即清除,只有在下一次调用ThreadLocalMap的set()、get()或remove()方法时才会触发Entry对象的清除操作。这是因为ThreadLocalMap中的Entry对象使用了弱引用,只有在下一次调用ThreadLocalMap时才会被垃圾回收器扫描到并被清除。因此,使用ThreadLocal对象时需要注意,在不再需要使用ThreadLocal对象时,应该及时调用remove()方法,以便及时清除ThreadLocalMap中对应的Entry对象,从而避免内存泄漏。当我们在写API接口的时候,通常Controller层会接受来自前端的入参,当这个接口功能比较复杂的时候,可能我们调用的Service层内部还调用了很多其他的很多方法,通常情况下,我们会在每个调用的方法上加上需要传递的参数。但是如果我们将参数存入ThreadLocal中,那么就不用显式的传递参数了,而是只需要ThreadLocal中获取即可。这个场景其实使用的比较少,一方面显式传参比较容易理解,另一方面我们可以将多个参数封装为对象去传递。在现在的系统设计中,前后端分离已基本成为常态,分离之后如何获取用户信息就成了一件麻烦事,通常在用户登录后, 用户信息会保存在Session或者Token中。这个时候,我们如果使用常规的手段去获取用户信息会很费劲,拿Session来说,我们要在接口参数中加上HttpServletRequest对象,然后调用 getSession方法,且每一个需要用户信息的接口都要加上这个参数,才能获取Session,这样实现就很麻烦了。在实际的系统设计中,我们肯定不会采用上面所说的这种方式,而是使用ThreadLocal,我们会选择在拦截器的业务中, 获取到保存的用户信息,然后存入ThreadLocal,那么当前线程在任何地方如果需要拿到用户信息都可以使用ThreadLocal的get()方法 (异步程序中ThreadLocal是不可靠的, 后续会出文章详解)。当用户登录后,会将用户信息存入Token中返回前端,当用户调用需要授权的接口时,需要在header中携带 Token,然后拦截器中解析Token,获取用户信息,调用自定义的类存入ThreadLocal中,当请求结束的时候,将ThreadLocal存储数据清空(这一点很重要,否则会产生内存泄漏), 中间的过程无需再关注如何获取用户信息,只需要使用工具类的get方法即可。ThreadLocal的设计天然就做到了线程隔离。所以就不会出现线程安全问题。感谢各位的阅读,以上就是“Java多线程之ThreadLocal怎么使用”的内容了,经过本文的学习后,相信大家对Java多线程之ThreadLocal怎么使用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是百云主机,小编将为大家推送更多相关知识点的文章,欢迎关注!

相关推荐: linux下不解析php如何解决

这篇文章主要介绍“linux下不解析php如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“linux下不解析php如何解决”文章能帮助大家解决问题。 在使用Linux系统部署Web应用时,很多人会遇到一个很奇怪的…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 07/05 11:19
下一篇 07/05 11:19

相关推荐