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} 】

 

posted @ 2017-08-16 21:03  半生戎马,共话桑麻、  阅读(118)  评论(0)    收藏  举报
levels of contents