现在的位置: 首页 > 综合 > 正文

ThreadLocal 的整理

2013年08月03日 ⁄ 综合 ⁄ 共 5829字 ⁄ 字号 评论关闭

ThreadLocal

1.是什么。

ThreadLocal是一个线程局部变量的类

ThrealLocal 在解决非同步锁synchronized状态下的线程安全有重要意义的。(性能)

我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度。这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突。

我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源。但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享。

虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题。

按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但Spring的DAO模板类并未采用线程同步机制,因为线程同步限制了并发访问,会带来很大的性能损失。

此外,通过代码同步解决性能安全问题挑战性很大,可能会增强好几倍的实现难度。那模板类究竟仰丈何种魔法神功,可以在无需同步的情况下就化解线程安全的难题呢?答案就是ThreadLocal!

ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的作用。要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡垒。

其他例子:

ThreadLocal  ~ Map  用来保存线程局部变量

2. 做什么,

为解决线程同步问题提供了一种新的思路

3.怎么用:

需要注意到一点是:ThreadLocal保存的临界资源类型。像Connection这种。需要的资源,线程完了,不用了,就释放掉。不是一个修改型的临界资源。

例如,银行取钱,

用到了临界资源Session

就是这个例子很好理解。两个线程不能同时使用一个Connection,此时,解决的办法有两个,

1.使用synchronized,同步锁,把connection锁住,一个线程使用完后,其他的线程才能使用。

2.使用ThreadLocal,每个线程拥有与此connection“想等的局部对象”是通过new出来的。(即,如果线程局部变量没有这个对象,就new一个出来)

public class ThreadLocal<T>
extends Object

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 getset 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

例如,以下类生成对每个线程唯一的局部标识符。线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,在后续调用中不会更改。

import java.util.concurrent.atomic.AtomicInteger;
public class UniqueThreadIdGenerator {
        // 这个是要保存的局部变量,像Connection
	private static final AtomicInteger uniqueId = new AtomicInteger(0);
	private static final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {
			return uniqueId.getAndIncrement();
		}
	};
	public static int getCurrentThreadId() {
		return uniqueId.get();
	}
}


其中要注意重写那一段的代码,以前很少这样,在new一个实例对象的时候修改了ThreadLocal(或者其他类的方法)里面的方法。那个

在ThreadLocal中。initialValue()方法是被定义为 protected 的,明显是为了让子类重写的。

   protected T initialValue() {
        return null;
    }

下面这个是Hibernate中一个典型的ThreadLocal应用。

private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

3.怎么用

我现在已经知道了ThreadLocal是用来解决什么问题的。(线程同步的另一种思路)通过new 新的临界资源副本,保存到相应线程中。供该线程使用。

当我了解以上需求时,我考虑该在什么地方。。怎么样合理地来使用ThreadLocal(可以理解为一个map),

1.临界资源,同步资源等问题,发生在两种地方,

a.同线程的不同模块(对同一资源的操作)

b.不同线程的同一模块(........)

c.不同线程的不同模块(........)

先前,我们解决这种问题的首先想到的就是同步锁,对操作临界资源的代码块加锁(嵌套锁)。但是这很影响效率,

ThreadLocal就是另一种思路的产物,及线程局部变量(实际是new一个)。注意上面的a,b,c三种情况下,我们就需要临界资源的副本。以免不同线程/模块,操作统一临界资源,发生错误。

即在

a.同线程的不同模块,我们需要线程局部变量

b.不同线程的同一模块,我们需要......

c.不同线程的不同模块,我们需要......

归纳来看,我们需要临界资源的地方,都要取副本,不能直接使用本体

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;

public class ThreadLocalTest {
	// private static ThreadLocal<Integer> dataMap = new ThreadLocal<Integer>();
	private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();

	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();
					System.out.println(Thread.currentThread().getName()
							+ " has put data :" + data);
					// dataMap.set(data);
					// 这里是一个基本类型变量,
					// 可以放一个对象。包含很多数据的对象,或者集合
					/*
					 * MyThreadScopeData myData = new MyThreadScopeData();
					 * myData.setName("name" + data); myData.setAge(data);
					 * map.set(data);
					 */
					MyThreadScopeData.getThreadInstance()
							.setName("name" + data);// 本线程相关的对象,
					MyThreadScopeData.getThreadInstance().setAge(data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}

	static class A {
		public void get() {
			// int data = dataMap.get();
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("A from" + Thread.currentThread().getName()
					+ " get Mydata :" + myData.getName() + "  "
					+ myData.getAge());

		}
	}

	static class B {
		public void get() {
			// int data = dataMap.get();
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("B from" + Thread.currentThread().getName()
					+ " get Mydata :" + myData.getName() + "  "
					+ myData.getAge());

		}
	}
}

/**
 * 这是一个封装好了要用的线程局部变量的类。
 * 使用到了ThreadLocal,当在线程中调用这个类的getInstance()方法,能返回临界资源的“副本”,所以这个类的名字应该明确
 * ,返回什么副本对象,名字就应该是什么。 例如,是Connection,则我把它命名为ThreadScopeConnection.getInstance()
 * 就返回一个线程范围内的Connection
 * 
 * @author jake
 * 
 */
class ThreadScopeConnection {
	private static Connection conn = null;
	private static ThreadLocal<Connection> map = new ThreadLocal<Connection>();

	private ThreadScopeConnection() {

	}

	public static Connection getThreadScopeInstance() {
		Connection instance = map.get();
		// 这里只是为了让getConnection不报错
		String url = null;
		String user = null;
		String password = null;
		if (instance == null) {
			try {
				instance = DriverManager.getConnection(url, user, password);
			} catch (SQLException e) {
				e.printStackTrace();
			}
			map.set(instance);
		}
		return instance;
	}

}

/**
 * 这个例子就不够明确,返回的是一个data?什么数据,不知道。
 * 当然它是正确的,但是它不好,它把两个对象封装到一个了,原本应该是--一个数据对象,和一个线程范围局部变量对象 *
 * 
 * @author jake
 * 
 */
class MyThreadScopeData {
	// 临界资源的副本.这里资源不一定是MyThreadScopeData这个类,也可以是Session,Connection这种对象,更好理解。
	// private static MyThreadScopeData instance = new MyThreadScopeData();
	private static MyThreadScopeData instance = null;
	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();

	private MyThreadScopeData() {

	}

	// 重构快捷键 Shift + Alt + R
	/**
	 * 以下是ThreadLocal的get()方法 public T get() { Thread t =
	 * Thread.currentThread(); //以当前线程为key取存放在map中的数据对象。 ThreadLocalMap map =
	 * getMap(t); if (map != null) { ThreadLocalMap.Entry e =
	 * map.getEntry(this); if (e != null) return (T)e.value; } return
	 * setInitialValue(); }
	 */
	public static MyThreadScopeData getThreadInstance() {
		MyThreadScopeData instance = map.get();
		if (instance == null) {
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}

	

	private String name;
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

}

以上是摘出自黑马训练营的一段代码,有点不好的地方,而且老师也没有很明白地讲明。自己小结了下。

另外:Iteye的这篇博文还是对我很有帮助的。

http://www.iteye.com/topic/103804

多追究下去不是我本意,本篇学习日记就到这,以后再实际项目中希望能慢慢深入

抱歉!评论已关闭.