关于ThreadLocal

<参考一>

首先有几点需要大家清楚的:

1、  ThreadLocal只是对需要存储的对象的管理,而存储实际是由当前Thread负责。个人理解为ThreadLocal是一个操作Thread. threadLocals 的工具。

2、  使用ThreadLocal可以使对象达到线程隔离的目的。同一个ThreadLocal操作不同的Thread,实质是各个Thread对自己的变量操作。

3、  为什么要使用ThreadLocal,个人感觉有两个原因,1是与其它线程的隔离,2是可以在一个线程的生命周期中使用同一个对象,达到对象传递的作用。这样的好处是可以减少dal访问或者ws调用。

 

我这里列出一个用到ThreadLocal的例子,主要的作用是使用ThreadLocal记录用户信息以及记录用户的执行时间。这在实际应用中,可以映射为全局记录用户的权限,以及使用Threadlocal对系统的性能做一些分析等。。

 

首先有两个对象,一个是用户对象

 

Java代码  
// 简单记录用户是否可以访问,可以用于全局权限控制等  
class User {  
    private String name;  
    private boolean isAllow;  
    public User(String name, boolean isAllow) {  
        this.name = name;  
        this.isAllow = isAllow;  
    }  
    public String getName() {  
        return name;  
    }  
    public boolean isAllow() {  
        return isAllow;  
    }  
    @Override  
    public String toString() {  
        return "用户名:" + name + "\t 是否允许访问:" + isAllow;  
    }  
}

另一个是消费时间对象

Java代码  
    // 用于记录每一步骤耗时…,可以用于每一步的性能分析  
    class TimeConsumer {  
        // 名称  
        private String name;  
        // 耗时数据列表  
        private List<Long> steps;  
        public TimeConsumer(String name, long start) {  
            this.name = name;  
            steps = new ArrayList<Long>();  
            steps.add(start);  
        }  
        public void andStep(long step) {  
            steps.add(step);  
        }  
        @Override  
        public String toString() {  
            StringBuffer br = new StringBuffer("操作[" + name + "]共有"  
                    + (steps.size() - 1) + "步\n");  
            for (int i = 1; i < steps.size(); i++) {  
                br.append("\t|--耗时[" + (steps.get(i) - steps.get(0))  
                        + "ms]\n");  
            }  
            br.append("\n");  
            return br.toString();  
        }  
    }  

 

接下来,建立一个对这两个对象管理的ThreadLocal的类

Java代码  
    // threadlocal 管理类  
    class MyThreadLocal {  
        // 用于全局记录user访问权限  
        private ThreadLocal<User> userLocal;  
        // 用于全局记录用户每一步的耗时  
        private ThreadLocal<TimeConsumer> timeLocal;  
        private static MyThreadLocal local = new MyThreadLocal();  
        private MyThreadLocal() {  
            userLocal = new ThreadLocal<User>();  
            timeLocal = new ThreadLocal<TimeConsumer>();  
        }  
        public static MyThreadLocal getInstanse() {  
            return local;  
        }  
        public void addUser(User user) {  
            userLocal.set(user);  
        }  
        public User getUser() {  
            return userLocal.get();  
        }  
        public void addTime(TimeConsumer timeConsumer) {  
            timeLocal.set(timeConsumer);  
        }  
        public void addTime(long l) {  
            TimeConsumer time = timeLocal.get();  
            timeLocal.remove();  
            time.andStep(l);  
            timeLocal.set(time);  
        }  
        public TimeConsumer getTime() {  
            return timeLocal.get();  
        }  
    }  

 

接下来就可以对Threadlocal进行测试了。为了模拟多线程,我这里自己实现了多线程

Java代码  
    public class CoreThreadLocal {  
        public static void main(String[] args) {  
            new Thread(new TestRunnable("name1", 1000L, true)).start();  
            new Thread(new TestRunnable("name2", 700L, true)).start();  
            new Thread(new TestRunnable("name3", 888, false)).start();  
        }  
    }  
      
    // 用于测试,多线程实现  
    class TestRunnable implements Runnable {  
        String name;  
        long l;  
        boolean isAllow;  
      TestRunnable(String name, long l, boolean isAllow) {  
            this.name = name;  
            this.l = l;  
            this.isAllow = isAllow;  
        }  
        public void run() {  
            MyThreadLocal local = MyThreadLocal.getInstanse();  
            local.addUser(new User(name, isAllow));  
            local.addTime(new TimeConsumer(name, System.currentTimeMillis()));  
            // 做某个业务,并记录时间  
            doThings(l);  
            local.addTime(System.currentTimeMillis());  
            // 做某个业务,并记录时间  
            doThings(l);  
            local.addTime(System.currentTimeMillis());  
            // 业务做完,打印日志  
            System.out.println(local.getUser());  
            System.out.println(local.getTime());  
        }  
        // 模拟具体业务的处理步骤  
        private void doThings(long l) {  
            try {  
                Thread.sleep(l);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  

 

运行上面的程序得到结果如下:

Java代码  
    用户名:name2    是否允许访问:true  
    操作[name2]共有2步  
        |--耗时[703ms]  
        |--耗时[1406ms]  
      
      
    用户名:name3    是否允许访问:false  
    操作[name3]共有2步  
        |--耗时[891ms]  
        |--耗时[1781ms]  
      
      
    用户名:name1    是否允许访问:true  
    操作[name1]共有2步  
        |--耗时[1000ms]  
        |--耗时[2000ms] 

 

<参考二>

ThreadLocal的用法

第一种方法:
package com.sodao.lucene;
import java.util.Random;

public class ThreadLocalTest implements Runnable{
ThreadLocal
<Studen> studenThreadLocal = new ThreadLocal<Studen>(); @Override public void run() { String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " is running..."); Random random = new Random(); int age = random.nextInt(100); System.out.println(currentThreadName + " is set age: " + age); Studen studen = getStudent(); //通过这个方法,为每个线程都独立的new一个student对象,每个线程的的student对象都可以设置不同的值 studen.setAge(age); System.out.println(currentThreadName + " is first get age: " + studen.getAge()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( currentThreadName + " is second get age: " + studen.getAge()); } private Studen getStudent() { Studen studen = studenThreadLocal.get(); if (null == studen) { studen = new Studen(); studenThreadLocal.set(studen); } return studen; } public static void main(String[] args) { ThreadLocalTest t = new ThreadLocalTest(); Thread t1 = new Thread(t,"Thread A"); Thread t2 = new Thread(t,"Thread B"); t1.start(); t2.start(); } } class Studen{ int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

 

第二种方法: 

package com.sodao.lucene;

import java.util.Random;

public class MultiThreadTest  implements Runnable{
    
    Studen studen = new Studen();
    
    @Override
    public void run() {
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running ....");
        //同步
        synchronized (studen) {
            Random random = new Random();
            int age = random.nextInt(100);
            studen.setAge(age);
            System.out.println(currentThreadName + " is set age: " + age);
            System.out.println(currentThreadName + "is first get age: " + studen.getAge() );
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(currentThreadName + " is second get age: " + studen.getAge() );
        }
    }
    
    public static void main(String[] args) {
        MultiThreadTest m = new MultiThreadTest();
        Thread t1 = new Thread(m,"Thread A");
        Thread t2 = new Thread(m,"Thread B");
        t1.start();
        t2.start();
    }
}

class Student {
    int age;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }    
}

以上2中方法都实现的功能相同,但方法不一样

ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

ThreadLocal 和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区 别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本, 使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通 信时能够获得数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。

ThreadLocal使用的一般步骤

1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。

 

<参考三>

 

ThreadLocal用法和实现原理

 

如果你定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap。并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在 跨线程的意义。那么你不要sychronize这么复杂的东西,ThreadLocal将是你不错的选择。

举例来说:

import java.util.HashMap;

public class TreadLocalTest {

    static ThreadLocal<HashMap> map0 = new ThreadLocal<HashMap>(){
        @Override
        protected HashMap initialValue() {
            System.out.println(Thread.currentThread().getName()+"initialValue");
            return new HashMap();
        }
    };
    public void run(){
        Thread[] runs = new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new T1(i));
        }
        for(int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }
    public static class T1 implements Runnable{
        int id;
        public T1(int id0){
            id = id0;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            HashMap map = map0.get();
            for(int i=0;i<10;i++){
                map.put(i, i+id*100);
                try{
                    Thread.sleep(100);
                }catch(Exception ex){
                }
            }
            System.out.println(Thread.currentThread().getName()+':'+map);
        }
    }
    /**
     * Main
     * @param args
     */
    public static void main(String[] args){
        TreadLocalTest test = new TreadLocalTest();
        test.run();
    }

}

输出解释;

Thread-1:start
Thread-2:start
Thread-0:start
Thread-2initialValue
Thread-1initialValue
Thread-0initialValue
Thread-1:{0=100, 1=101, 2=102, 3=103, 4=104, 5=105, 6=106, 7=107, 8=108, 9=109}
Thread-2:{0=200, 1=201, 2=202, 3=203, 4=204, 5=205, 6=206, 7=207, 8=208, 9=209}
Thread-0:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}

可以看到map0 虽然是个静态变量,但是initialValue被调用了三次,通过debug发现,initialValue是从map0.get处发起的。而且每个线程都有自己的map,虽然他们同时执行。

进入Theadlocal代码,可以发现如下的片段;

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

这说明ThreadLocal确实只有一个变量,但是它内部包含一个map,针对每个thread保留一个entry,如果对应的thread不存在则会调用initialValue。

 

posted @ 2016-05-10 16:53  疏婴  阅读(100)  评论(0)    收藏  举报