06-ThreadLocal类及应用技巧
ThreadLocal是什么
早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
- void set(Object value)设置当前线程的线程局部变量的值。
- public Object get()该方法返回当前线程所对应的线程局部变量。
- public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
范例:
package cn.itcast.demo.thread; import java.util.Random; public class ThreadLocalTest { //private static Map<Thread, Integer> threadMap = new HashMap<Thread, Integer>(); 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(); //threadMap.put(Thread.currentThread(), data); threadLocal.set(data); // 替换上面的代码 System.out.println(Thread.currentThread().getName() + " has put data: " + data); new A().get(); new B().get(); } }).start(); } } // A模块 static class A { public void get() { //int data = threadMap.get(Thread.currentThread()); int data = threadLocal.get(); // 替换上面的代码 System.out.println("A from " + Thread.currentThread().getName() + " get data: " + data); } } // B模块 static class B { public void get() { //int data = threadMap.get(Thread.currentThread()); int data = threadLocal.get(); // 替换以上的代码 System.out.println("B from " + Thread.currentThread().getName()+ " get data: " + data); } } }
输出:
Thread-0 has put data: -231837341 Thread-1 has put data: -471011850 A from Thread-1 get data: -471011850 A from Thread-0 get data: -231837341 B from Thread-0 get data: -231837341 B from Thread-1 get data: -471011850
一个ThreadLocal对象只能存放一个变量,如果要放多个变量,那么就创建多个ThreadLocal对象,或者把所有属性都封装成一个实体类(或者集合),把整个实体类对象放入到ThreadLocal对象中,就可以实现多个变量存放的操作
package cn.itcast.demo.thread; import java.util.Random; public class ThreadLocalTest { private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); // 当前线程的局部变量 public static void main(String[] args) { // 两个线程 for (int i=0; i<2; i++) { new Thread(new Runnable() { @Override public void run() { // 为了区别不同的数据,这里需要一个随机数 int args = new Random().nextInt(100); // 准备放入线程的数据 User user = new User(); user.setUsername("Peter: " + args); user.setAge(20 + args); // 放入当前线程的局部变量中 threadLocal.set(user); // 控制台打印结果 System.out.println(Thread.currentThread().getName() + " has put data: " + user); // 线程调用模块 new A().get(); new B().get(); } }).start(); } } // A模块 static class A { public void get() { User user = threadLocal.get(); System.out.println("A from " + Thread.currentThread().getName() + " get data: " + user); } } // B模块 static class B { public void get() { User user = threadLocal.get(); System.out.println("B from " + Thread.currentThread().getName() + " get data: " + user); } } } // User实体类 class User { // 两个属性 private String username; private int age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "【 username: {" + this.username + "}, {age: " + this.age + "} 】"; } }
打印结果:
Thread-1 has put data: 【 username: {Peter: 18}, {age: 38} 】 Thread-0 has put data: 【 username: {Peter: 16}, {age: 36} 】 A from Thread-1 get data: 【 username: {Peter: 18}, {age: 38} 】 A from Thread-0 get data: 【 username: {Peter: 16}, {age: 36} 】 B from Thread-0 get data: 【 username: {Peter: 16}, {age: 36} 】 B from Thread-1 get data: 【 username: {Peter: 18}, {age: 38} 】
改善以上的代码,因为一个线程就需要一个ThreadLocal变量,如果有很多个线程,那么就要很多个变量,这样性能大大降低,所以要使用单例设计模式
以后任何一个模块或者线程调用这个实体类,就会自带线程范围变量,因为这个类在设计的时候本身就具有线程范围的变量的特点
应用场景:
package cn.itcast.demo.thread; import java.util.Random; public class ThreadLocalTest { private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); // 当前线程的局部变量 public static void main(String[] args) { // 两个线程 for (int i=0; i<2; i++) { new Thread(new Runnable() { @Override public void run() { // 为了区别不同的数据,这里需要一个随机数 int args = new Random().nextInt(100); // 准备放入线程的数据 User user = User.getThreadInstance(); user.setUsername("Peter: " + args); user.setAge(args); // 放入当前线程的局部变量中 threadLocal.set(user); // 控制台打印结果 System.out.println(Thread.currentThread().getName() + " has put data: " + user); // 线程调用模块 new A().get(); new B().get(); } }).start(); } } // A模块 static class A { public void get() { User user = User.getThreadInstance(); System.out.println("A from " + Thread.currentThread().getName() + " get data: " + user); } } // B模块 static class B { public void get() { User user = User.getThreadInstance(); System.out.println("B from " + Thread.currentThread().getName() + " get data: " + user); } } } // User实体类, 【单例设计模式】 class User { // 1.构造私有化 private User() {} // 2.提高外界创建本实例对象的静态方法【可以使用懒汉式,也可以使用饿汉式(使用饿汉式时要考虑到线程的安全问题)】 private static User user = null; private static ThreadLocal<User> userThread = new ThreadLocal<User>(); public static synchronized User getThreadInstance() { user = userThread.get(); if (user == null) { // 这里在赋值之前可能会出现线程不安全,即在A线程赋值之前,B线程也进来了,所以要+synchronized进行线程互斥,饿汉式不需要 user = new User(); userThread.set(user); } return user; } /* 饿汉式 User user = new User(); public static User getThreadInstance() { return user; } */ private String username; private int age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "【 username: {" + this.username + "}, {age: " + this.age + "} 】"; } }
打印结果:
Thread-0 has put data: 【 username: {Peter: 52}, {age: 52} 】 Thread-1 has put data: 【 username: {Peter: 79}, {age: 79} 】 A from Thread-0 get data: 【 username: {Peter: 52}, {age: 52} 】 A from Thread-1 get data: 【 username: {Peter: 79}, {age: 79} 】 B from Thread-0 get data: 【 username: {Peter: 52}, {age: 52} 】 B from Thread-1 get data: 【 username: {Peter: 79}, {age: 79} 】