作者微信 bishe2022

代码功能演示视频在页面下方,请先观看;如需定制开发,联系页面右侧客服
ThreadLocal的使用与代码示例(译)

Custom Tab

Thread Local是一个有趣且实用的概念,却是很多开发人员不知如何使用的。在本篇文章中,我将给大家解释什么是ThreadLocal和如何使用,并附带示例代码。
由于这个概念在开始有些难以理解,所以我将解释地尽可能简单(注:你不使用这段示例代码,因为它是在生产环境上的。掌握它的概念并加以改善,,,笔者已将其改为可单独运行)

什么是ThreadLocal

ThreadLocal可以理解为变量访问的范围,例如request、session范围,而这里是线程范围的。你可以在ThreadLocal中设置任何对象,该对象对特定的线程的访问将是
“全局”并且是“局部”的。“全局”又是“局部”?让我解释它:
◆ 保存在ThreadLocal中的值对线程来说是全局的,意味着在这个线程的任何地方都可以访问到。如果这个线程调用了经过几个类的方法,那么所以这些类的方法都能看到其
他方法(因为他们在相同的线程中执行)在这个ThreadLocal中设置的变量。这个值不需要被显示的传递。看起来你使用的是全局的变量。
◆ 保存在ThreadLocal中的值对线程来说又是局部的。意味着每个线程拥有它自己的ThreadLocal变量。一个线程不能访问/修改其他线程的ThreadLocal变量
好了,这就是ThreadLocal的概念,希望你已经理解它(如果没有,请留言)

使用时候使用ThreadLocal?

上段中已经看到了什么是ThreadLocal。现在让我们讨论下你什么时候用ThreadLocal这样情况。
我说明一种使用ThreadLocal的情况。设置你使用了Servlet调用一些业务方法。你有个需求即:为记录日志每一个请求该servlet的过程生成一个唯一的业务标识并把它传递到业务方法中。
一个解决方法是把这个业务标识作为一个参数传递给每一个方法。但这不是不念旧恶好的解决方案因为代码是冗余且没有必要的。
要解决它,就要用到ThreadLocal,你生成一个业务标识并把它设置到ThreadLocal中,在这之后,任何一个业务方法,这个servlet调能从这个变量中访问业务标识。
这个Servlet可能服务于不止一个请求。因为每个请求都是一个线程,因此业务标识对每个线程是唯一的(局部),且对线程的整个执行过程是可见的(全局)。

如何使用ThreadLocal

Java提供了ThreadLocal类,你可以设置获取线程范围的变量。下面是一个示例代码演示了上面解释的东西。
下面是Context.java文件,该类包含了业务标识字段。

[java] 
/* 
 * 包含业务唯一标识的类 
 * */  
public class Context {  
    private String transactionId;  
  
    public String getTransactionId() {  
        return transactionId;  
    }  
  
    public void setTransactionId(String transactionId) {  
        this.transactionId = transactionId;  
    }  
  
}

下面创建MyThreadLocal.java类,它做为引用Context对象的容器

[java] 
/** 
 * 其中引用了Context类 
 */  
public class MyThreadLocal {  
    private static final ThreadLocal<Context> userThreadLocal = new ThreadLocal<Context>();  
    public static void set(Context user){  
        userThreadLocal.set(user);  
    }  
    public static void unset(){  
        userThreadLocal.remove();  
    }  
    public static Context get(){  
        return userThreadLocal.get();  
    }  
      
}

上面的代码,创建了一个静态的ThreadLocal字段,可以使用它的set/get方法设计ThreadLocal变量。
下面创建主类:ThreadLocalDemo.java。生成并将业务标识设置到ThreadLocal中然后在业务方法中调用。

[java] 
public class ThreadLocalDemo implements Runnable{  
    private static AtomicInteger ai = new AtomicInteger(0);  
    public void run() {  
        Context context = new Context();  
        context.setTransactionId(getName());  
        MyThreadLocal.set(context);  
        System.out.println("request["+Thread.currentThread().getName()+"]:"+context.getTransactionId());  
        new BusinessService().businessMethod();  
        MyThreadLocal.unset();  
    }  
      
    private String getName() {  
        return ai.getAndIncrement()+"";  
    }  
  
    public static void main(String[] args) {  
        ThreadLocalDemo tld  = new ThreadLocalDemo();  
        new Thread(tld).start();  
        new Thread(tld).start();  
    }  
  
}  
public class BusinessService {  
  
    public void businessMethod() {  
        Context context = MyThreadLocal.get();  
        System.out.println("service["+Thread.currentThread().getName()+"]:"+context.getTransactionId());  
    }  
  
}

运行结果:

request[Thread-0]:0
request[Thread-1]:1
service[Thread-1]:1
service[Thread-0]:0

从上面看来,我们即使没有明确地传递业务标识,它的值仍然可以从业务方法中访问到并打印出来。另外在两个线程中的
业务标识字段互不相同(分别是0和1)


补充
ThreadLocal提及的一个重要的好处是,就是作为同步关键字synchronized的替代方案,在强调事务的环境(in transaction-intensive )
中提供扩展性。封装在ThreadLocal的以优美而简洁的方式自动变得线程安全。因为它能保存在不同的线程中非共享的。

“在不同线程中不会共享ThreadLocal变量”并不完全正确,如果ThreadLocal存在继承关系的话,在线程间可以共享变量(未验证)

下面是JDK官方的说明和例子

ThreadLocal
即线程局部变量,这种变量不同于普通变量,因为访问变量的每个线程都有自己局部变量,
独立于变量的初始化副本。ThreadLocal实例一般作为一个私有的静态字段存在于类中,它与线程的状态相关联。

比如,下面的类中为每个线程生成了局部唯一标识符。某个线程的id第一次调用
UniqueThreadIdGenerator.getCurrentThreadId()方法时分配,在随后的调用中在各自线程中增加,互不影响。

[java] 
public class UniqueThreadIdGenerator {  
    private static final AtomicInteger uniqueId = new AtomicInteger(0);  
  
    private static final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() {  
        protected Integer initialValue() {  
            System.out.println("Called by: " + Thread.currentThread().getName());  
            return uniqueId.getAndIncrement();  
        }  
    };  
  
    public static int getCurrentThreadId() {  
        return uniqueNum.get();  
    }  
    public static void setCurrentThreadId(Integer value) {  
        uniqueNum.set(value);  
    }  
}  
public class ThreadLocalDemo implements Runnable {  
    private int prev = -1;  
  
    @Override  
    public void run() {  
        try {  
            Thread.sleep(1000);  
        } catch (Exception e) {  
        }  
        for (int j = 0; j < 10; j++) {  
            int curr = UniqueThreadIdGenerator.getCurrentThreadId();  
            if (prev != -1) {  
                System.err.println("[FAIL]: curr=" + curr + ", prev=" + prev);  
            }  
            System.out.println(Thread.currentThread().getName() + ": " + curr);  
  
            try {  
                Thread.sleep(100);  
            } catch (Exception e) {  
            }  
            UniqueThreadIdGenerator.setCurrentThreadId(curr+1);  
        }  
    }  
  
    public static void main(String[] args) {  
        int count = 2;  
        ThreadLocalDemo tld = new ThreadLocalDemo();  
        for (int i = 0; i < count; i++) {  
            Thread t = new Thread(tld);  
            t.setName("T-" + i);  
            t.start();  
        }  
    }  
  
}

每个线程都持有一个线程局部变量副本的隐式引用,只要这个线程是活动且可访问的。当线程销毁时,它的所有线程

局部变量副本也会跟着被垃圾回收器回收,除非存在这个变量的其他引用


下面是jdk源码的ThreadLocal类图

qqqq.jpg

转载自:http://blog.csdn.net/mylovepan/article/details/19963895


















Home