JAVA多线程之ThreadLocal

来源:互联网 发布:2017网络新词 编辑:程序博客网 时间:2024/04/30 03:47

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

package com.fei;/** * 每个线程代表一所学校,学校下面有年级 * */public class DataShare {public static void main(String[] args) throws InterruptedException {Thread qinghua=new Thread(new Runnable(){@Overridepublic void run() {School.getSchool().setName("清华大学");//修改学校名称new GradeOne().print();new GradeTwo().print();}});qinghua.setName("qinghua");qinghua.start();qinghua.join();Thread beijing =new Thread(new Runnable(){@Overridepublic 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(){@Overridepublic void run() {School.getSchool().setName("清华大学");//修改学校名称new GradeOne().print();new GradeTwo().print();}});qinghua.setName("qinghua");qinghua.start();qinghua.join();Thread beijing =new Thread(new Runnable(){@Overridepublic 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);    }

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