JAVA多线程之ThreadLocal

       使用过单例模式的同学都知道,如果不做一些特殊处理,那么不管是谁通过getInstance()获取到的实例对象都是同一个的,如果某人获得实例对象后,修改一些属性值,那么所有人都会受到影响,不管是同线程内的同学,还是不同线程的同学,均会受到影响。例如下面例子。

package com.fei;
/**
 * 每个线程代表一所学校,学校下面有年级
 *
 */
public class DataShare {
	public static void main(String[] args) throws InterruptedException {
		Thread qinghua=new Thread(new Runnable(){
			@Override
			public void run() {
			School.getSchool().setName("清华大学");//修改学校名称
			new GradeOne().print();
			new GradeTwo().print();
			}
		});
		qinghua.setName("qinghua");
		qinghua.start();
		qinghua.join();
	Thread beijing =new Thread(new Runnable(){
		@Override
		public void run() {
			//该学校发现School类中的学校名称和该校一样,故使用默认学校
			new GradeOne().print();
			new GradeTwo().print();
		}
	});
	beijing.setName("beijing");
	beijing.start();
	}
}
class School{
	private  String name="北京大学";
	private static School instance=null;
	
	private School(){};
	public  static School getSchool(){
		if(instance==null){
			synchronized(School.class){
				if(instance==null)
					instance=new School();
			}
		}
		return instance;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

}
class GradeOne{
	public void print(){
		System.out.println(Thread.currentThread().getName()+" :"+School.getSchool().getName()+"一年级");
	}
}
class GradeTwo{
	public void print(){
		System.out.println(Thread.currentThread().getName()+" :"+School.getSchool().getName()+"二年级");
	}
}


运行结果

qinghua :清华大学一年级
qinghua :清华大学二年级
beijing :清华大学一年级
beijing :清华大学二年级

          代码很简单,结果意料之中,因为清华大学和北京大学都使用了同一个School对象,所以清华大学修改学校名称时,北京大学也受到了影响。

         现在的需求时,清华大学修改学校名称时,北京大学不受影响。也就是说每个学校都拥有自己的School对象,其他学校不能干扰,但是同一所学校内的所有年级使用School对象时却又是同一个的。那么我们可以把程序这样来改。

package com.fei;

import java.util.HashMap;
import java.util.Map;

/**
 * 每个线程代表一所学校,学校下面有年级
 *
 */
public class DataShare {
	public static void main(String[] args) throws InterruptedException {
		Thread qinghua=new Thread(new Runnable(){
			@Override
			public void run() {
			School.getSchool().setName("清华大学");//修改学校名称
			new GradeOne().print();
			new GradeTwo().print();
			}
		});
		qinghua.setName("qinghua");
		qinghua.start();
		qinghua.join();
	Thread beijing =new Thread(new Runnable(){
		@Override
		public void run() {
			//该学校发现School类中的学校名称和该校一样,故使用默认学校
			new GradeOne().print();
			new GradeTwo().print();
		}
	});
	beijing.setName("beijing");
	beijing.start();
	}
}
class School{
	private  String name="北京大学";
	private static School instance=null;
	//把每个线程对应的学校放到map中来
	private static Map<Thread,School> map=new HashMap<Thread,School>();
	private School(){};
	/**
	 * 不需要使用synchronized了,因为就算多个线程同时进来,也是各取个的学校
	 */
	public  static School getSchool(){
		instance = map.get(Thread.currentThread());
		if (instance == null) {
			instance = new School();
			map.put(Thread.currentThread(), instance);
		}
		return instance;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

}
class GradeOne{
	public void print(){
		System.out.println(Thread.currentThread().getName()+" :"+School.getSchool().getName()+"一年级");
	}
}
class GradeTwo{
	public void print(){
		System.out.println(Thread.currentThread().getName()+" :"+School.getSchool().getName()+"二年级");
	}
}


 

运行结果:

qinghua :清华大学一年级
qinghua :清华大学二年级
beijing :北京大学一年级
beijing :北京大学二年级

 

      我们实现需求了。

 

      为了解决此类需求,在JDK1.5中,为我们提供了ThreadLocal这个类,我们可以将上面程序的School类做如下修改即可。为了节约篇幅,就只贴School类的源码了。

class School{
	private  String name="北京大学";
	private static School instance=null;
	//把每个线程对应的学校放到map中来
//	private static Map<Thread,School> map=new HashMap<Thread,School>();
	private static ThreadLocal<School> threadLocal=new ThreadLocal<School>();
	private School(){};
	/**
	 * 不需要使用synchronized了,因为就算多个线程同时进来,也是各取个的学校
	 */
	public  static School getSchool(){
//		instance = map.get(Thread.currentThread());
		instance=threadLocal.get();
		if (instance == null) {
			instance = new School();
//			map.put(Thread.currentThread(), instance);
			threadLocal.set(instance);
		}
		return instance;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

}

         

        上面的程序中我们用到了ThreadLocal类中的 set(T value)和get()方法,那么我们来看看他们的源码.

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//根据当前线程对象去取得当前线程中的ThreadLocalMap对象,是线程独自拥有的,不和其他线程公用
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

      我们暂不去过分追究它的详细实现,我们从这个源码段中了解大意。表面的大意太简单明了了,也就不多说了,接下来,看看setInitialValue()

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

      对于这方法,我们去看看initialValue()即可,其他的大意都很简单。

 

protected T initialValue() {
        return null;
    }


      该方法返回null,也就是说初始值是null。

    所以将上面的几个方法连起来看,就是get()如果ThreadLoal中还没T对象,则返回null。

    接下来看看set(T value)

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    这代码在上面出现过了,大意也很明了。


 

 
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值