线程系列5--java中的ThreadLocal类实现线程范围内的数据共享(二)

  ThreadLocal类可以理解成一个类似与map集合使用,以当前线程当做key 来使用,将线程氛围内需要共享的数据当做value,形成键值对的形式使用。ThreadLocal和线程同步机制都是为了解决多线程中对同一个变量的访问冲突问题。

  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

我还是以传智播客中的代码举例说明:下面代码是利用ThreadLocal来实现多线程的数据共享,代码很简单不做过多讲解。

/**
     * ThreadLocal 类似于map集合 只是集合的key是当前正在运行的线程  
     * 通过ThreadLocal可以将变量(或者是变量的容器)与当前线程相绑定.
     */
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() ;
    public static void main(String[] args) {
    	for(int i = 0 ;i<2 ;i++){
    		new Thread(new Runnable(){
	            @Override
	            public  void run() {
	            	int data = new Random().nextInt();
	            	threadLocal.set(data) ; //将数据绑定到带当前线程
	            	System.out.println(Thread.currentThread().getName()+ " put random data:"+data);
		        	new A().get() ;
		        	new B().get() ;
		        }
		        
		    }).start() ;
    	}
	}
	static class A {
	    public void get(){
	        //取数据都从当前线程中取得 
	    int d = threadLocal.get() ;
	    System.out.println("A from " + Thread.currentThread().getName() 
	            + " get data :" + d);
	    }
	}
	static class B{
	    public void get(){
	        //取数据都从当前线程中取得 
	    int d = threadLocal.get() ;
	    System.out.println("B from " + Thread.currentThread().getName() 
	            + " get data :" + d);
	    }
	}

当线程中要存放多个对象时怎么办那,显然我们要封装对象,将对象放进ThreadLocal中,此处不再书写相关代码了,我们只需要将封装的对象直接当做泛型放到ThreadLocal中即可,非常易懂;

代码继续升级改造,传智播客中是利用单例模式的思想,将我们要封装的对象提出来给与ThreadLocal封装。不过我觉得张孝祥老师在讲解时使用单例模式不太好,单例模式是在系统中只有一份,但是ThreadLocal是每个线程复制一个对象,是典型的利用空间换时间,每个线程都会对应一个封装的对象,并不想单例模式那样。

private static ThreadLocal<MyThreadLocal> threadLocal = new ThreadLocal<MyThreadLocal>();
    public static void main(String[] args) {
    	for(int i = 0 ;i<2 ;i++){
    		new Thread(new Runnable(){
	            @Override
	            public  void run() {
	            	int data = new Random().nextInt();
	            	MyThreadLocal.getMyThreadLocalInstant().setName("傲视苍穹--大泼猴-孙悟空"+data);
	            	MyThreadLocal.getMyThreadLocalInstant().setValue("傲视苍穹--大泼猴-杨婵"+data);
	            	System.out.println(Thread.currentThread().getName()+ " put random data:"+data);
		        	new A().get() ;
		        	new B().get() ;
		        }
		        
		    }).start() ;
    	}
	}
	static class A {
	    public void get(){
	    //取数据都从当前线程中取得 
	    MyThreadLocal myThreadLocal = MyThreadLocal.getMyThreadLocalInstant();
	    System.out.println("A from " + Thread.currentThread().getName() 
	            + " name :" + myThreadLocal.getName()+"value:"+myThreadLocal.getValue());
	    }
	}
	static class B{
	    public void get(){
	    //取数据都从当前线程中取得 
	    MyThreadLocal myThreadLocal = MyThreadLocal.getMyThreadLocalInstant();
	    System.out.println("B from " + Thread.currentThread().getName() 
	            + " name :" + myThreadLocal.getName()+"value:"+myThreadLocal.getValue());
	    }
	}
	static class MyThreadLocal{
		private MyThreadLocal(){}
		//单例模式:线程安全
//		private static MyThreadLocal myThreadLocal = null;
//		public static synchronized MyThreadLocal getMyThreadLocalInstant(){
//			if(myThreadLocal == null){
//				myThreadLocal = new MyThreadLocal();
//			}
//			return myThreadLocal;
//		}
		//使用单例模式的思想,来实现对象创建的思想
		private static ThreadLocal<MyThreadLocal> map = new ThreadLocal<>();
		
		public static MyThreadLocal getMyThreadLocalInstant(){
			if(map.get() == null){
				map.set(new MyThreadLocal());
			}
			return map.get();
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String getValue() {
			return value;
		}
		public void setValue(String value) {
			this.value = value;
		}
		private String name;
		private String value;
	}

上述代码利用单例模式的思想来管理ThreadLocal对象的创建,但是这样并不是说整个内存中只有一个对象,大家千万理解清楚,我听视频的时候就被这点迷惑了,这里只是用单例模式的思想来管理ThreadLocal对象的创建,每个线程都会创建一个被共享的数据对象放到ThreadLocal中。

下面内容摘自知乎用户,链接:https://www.zhihu.com/question/23089780/answer/62097840

可以总结为一句话:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

举个例子,我出门需要先坐公交再做地铁,这里的坐公交和坐地铁就好比是同一个线程内的两个函数,我就是一个线程,我要完成这两个函数都需要同一个东西:公交卡(北京公交和地铁都使用公交卡),那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路),我可以这么做:将公交卡事先交给一个机构,当我需要刷卡的时候再向这个机构要公交卡(当然每次拿的都是同一张公交卡)。这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的。

有人要说了:你可以将公交卡设置为全局变量啊,这样不是也能何时何地都能取公交卡吗?但是如果有很多个人(很多个线程)呢?大家可不能都使用同一张公交卡吧(我们假设公交卡是实名认证的),这样不就乱套了嘛。现在明白了吧?这就是ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。