Spring Cloud中使用Hystrix 线程隔离导致ThreadLocal数据丢失

在Spring Cloud中我们用Hystrix来实现断路器,默认是用信号量来进行隔离的,我们可以通过配置使用线程方式隔离。

在使用线程隔离的时候,有个问题是必须要解决的,那就是在某些业务场景下通过ThreadLocal来在线程里传递数据,用信号量是没问题的,从请求进来,但后续的流程都是通一个线程。

当隔离模式为线程时,Hystrix会将请求放入Hystrix的线程池中去执行,这个时候某个请求就有A线程变成B线程了,ThreadLocal必然消失了。

下面我们通过一个简单的列子来模拟下这个流程:

public class CustomThreadLocal {
	static ThreadLocal threadLocal = new ThreadLocal<>();
	
	public static void main(String[] args) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				CustomThreadLocal.threadLocal.set("猿天地");
				new Service().call();
				
			}
		}).start();
		
	}
}


class Service {
	public void call() {
		System.out.println("Service:" + Thread.currentThread().getName());
		System.out.println("Service:" + CustomThreadLocal.threadLocal.get());
		new Dao().call();
	}
}

class Dao {
	public void call() {
		System.out.println("==========================");
		System.out.println("Dao:" + Thread.currentThread().getName());
		System.out.println("Dao:" + CustomThreadLocal.threadLocal.get());
	}
}

我们在主类中定义了一个ThreadLocal用来传递数据,然后起了一个线程,在线程中调用Service中的call方法,并且往Threadlocal中设置了一个值

在Service中获取ThreadLocal中的值,然后再调用Dao中的call方法,也是获取ThreadLocal中的值,我们运行下看效果:

Service:Thread-0
Service:猿天地
==========================
Dao:Thread-0
Dao:猿天地

可以看到整个流程都是在同一个线程中执行的,也正确的获取到了ThreadLocal中的值,这种情况是没有问题的。

接下来我们改造下程序,进行线程切换,将调用Dao中的call重启一个线程执行:

public class CustomThreadLocal {
	static ThreadLocal threadLocal = new ThreadLocal<>();
	
	public static void main(String[] args) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				CustomThreadLocal.threadLocal.set("猿天地");
				new Service().call();
				
			}
		}).start();
		
	}
}


class Service {
	public void call() {
		System.out.println("Service:" + Thread.currentThread().getName());
		System.out.println("Service:" + CustomThreadLocal.threadLocal.get());
		//new Dao().call();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				new Dao().call();
			}
		}).start();
	}
}

class Dao {
	public void call() {
		System.out.println("==========================");
		System.out.println("Dao:" + Thread.currentThread().getName());
		System.out.println("Dao:" + CustomThreadLocal.threadLocal.get());
	}
}

再次运行,看效果:

Service:Thread-0
Service:猿天地
==========================
Dao:Thread-1
Dao:null

可以看到这次的请求是由2个线程共同完成的,在Service中还是可以拿到ThreadLocal的值,到了Dao中就拿不到了,因为线程已经切换了,这就是开始讲的ThreadLocal的数据会丢失的问题。

那么怎么解决这个问题呢,其实也很简单,只需要改一行代码即可:

static ThreadLocal threadLocal = new InheritableThreadLocal<>();

将ThreadLocal改成InheritableThreadLocal,我们看下改造之后的效果:

Service:Thread-0
Service:猿天地
==========================
Dao:Thread-1
Dao:猿天地

值可以正常拿到,InheritableThreadLocal就是为了解决这种线程切换导致ThreadLocal拿不到值的问题而产生的

至于原理大家可以去看jdk的源码,这边就不做过多讲解了,有了InheritableThreadLocal能为我们解决不少的问题。

欢迎加入我的知识星球,一起交流技术,免费学习猿天地的课程(http://cxytiandi.com/course)

PS:目前星球中正在星主的带领下组队学习Spring Cloud,等你哦!

微信扫码加入猿天地知识星球

猿天地

你可能感兴趣的:(spring,cloud)