Java并发编程---ThreadLocal

1、什么是ThreadLocal?

多线程共享变量的维护是很是头痛的问题,采用乐观悲观策略,悲观策略简单地作法咱们能够对共享变量加锁实现,可是锁的开销是比较大的,所以咱们也能够经过乐观策略,采用相似CAS(Compare And Set)的方法进行维护,固然,在读多写少的状况下,咱们还能够采用Copy-On-Write写时复制的来控制共享变量,其中最经典的实现那就是JDK的CopyOnWriteArrayList了。java

好了,说了这么多,下面正式进入正题。咱们能够想这么一个问题,在多线程环境中如何对每一条线程的生命周期进行跟踪,跟具体地说,在web项目中,咱们一般会对某一个请求从进入系统到退出系统的整个生命周期进行日志记录,又或者说是对一个事务的全过程进行跟踪记录,一般的作法是采用为每一个线程创建一个叫transactionId的值,用于标识每一个线程并进程跟踪,下图红圈即为transactionId的值。web

那么你可能会说,我也能够不使用transactionId来跟踪啊,我采用线程名不就行了吗?道理是这样,可是当你想对改值进行自定义呢?好比获取客户端的ip地址、被调用的接口名呢???数据库

好了,假设咱们采用transactionId来跟踪线程,那么若是这个transactionId是个共享变量的话,那咱们不就是得对其作相关线程同步的操做,线程那么多,那不烦死,笨死!嗯,TheadLocal就是在这种状况下而生了,一个TheadLocal表明一个变量,你千万不要觉得它表明一个线程,否则我会晕死!这个变量天生就是线程安全的。为何呢?由于它的工做原理是会为每条线程作一份变量的拷贝,各个线程的变量不会相互影响,天然就不会有线程安全问题咯!安全

酱紫,我画张图吧!bash

TheadLocal的底层实现原理是经过ThreadLocalMap实现,时间匆忙,其原理不在本文讲解范围内,读者自行学习源码!还有一点思想值得学习,多线程通常的同步机制采用了“以时间换空间”的方式,好比定义一个static变量,同步访问,而ThreadLocal则采用了“以空间换时间”的方式。session

2、实例解析

package com.wokao66.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {
	//线程本地变量
	private static ThreadLocal<Integer> sessionId = new ThreadLocal<>();
	public static void main(String[] args) {
		//线程1
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				//我对ThreadLocal的值进行修改,自定义
				sessionId.set(1);
				try {
					Thread.sleep(1000);
					//模拟一个线程的生命周期屡次获取ThreadLocal变量的值,验证是否在当前线程有作一份拷贝
					for (int i = 0; i < 5; i++) {
						System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
					}
				} catch (InterruptedException e) {}
			}
		});
		//线程2
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				//我对ThreadLocal的值进行修改,自定义
				sessionId.set(2);
				try {
					Thread.sleep(1000);
					//模拟一个线程的生命周期屡次获取ThreadLocal变量的值,验证是否在当前线程有作一份拷贝
					for (int i = 0; i < 5; i++) {
						System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
					}
				} catch (InterruptedException e) {}
			}
		});
		//线程1
		Thread thread3 = new Thread(new Runnable() {
			@Override
			public void run() {
				//我对ThreadLocal的值进行修改,自定义
				sessionId.set(3);
				try {
					Thread.sleep(1000);
					//模拟一个线程的生命周期屡次获取ThreadLocal变量的值,验证是否在当前线程有作一份拷贝
					for (int i = 0; i < 5; i++) {
						System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
					}
				} catch (InterruptedException e) {}
			}
		});
		ExecutorService service = Executors.newFixedThreadPool(30);
		service.execute(thread1);
		service.execute(thread2);
		service.execute(thread3);
	}
}
复制代码

控制台输出多线程

[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
复制代码

能够看到每条线程对应的ThreadLocal是同样的,证实了确实是有作拷贝操做!ide

3、ThreadLocal的应用场景

使用ThreadLocal首先一点那就是对变量的访问不须要同步控制,经常使用的场景以下:性能

  • 一、对多线程进行跟踪,经常使用于接口访问日志记录,能够参考MDC机制
  • 二、用来解决数据库链接、Session管理(其实也是一种跟踪用途)

须要注意的是,既然TreadLocal是以采用空间换取时间的思想,因此能够想象TreadLocal并不适合来定义大对象,由于大对象的话每一个线程都有拷贝,线程一多,性能一定受到牵连,甚至JVM抛出ERROR学习

相关文章
相关标签/搜索