线程特有对象
每个线程都创建一个该对象的实例,各个线程仅访问各自创建的实例,且一个线程不能访问另外一个线程创建的实例。这种各个线程创建各自的实例,一个实例只能被一个线程访问的对象就被称为线程特有对象(Thread Specific Object,TSO),相对应的线程就被称为该线程特有对象的持有线程。
线程特有对象既保障了对非线程安全对象的访问的线程安全,又避免了锁的开销。
ThreadLocal类相当于线程访问其线程特有对象的代理(Proxy),即各个线程通过这个对象可以创建并访问各自的线程特有对象,其类型参数T指定了相应线程特有对象的类型。
一个线程可以使用不同的ThreadLocal实例来创建并访问其不同的线程特有对象。多个线程使用同一个ThreadLocal实例所访问到的对象是类型T的不同实例,即这些线程各自的线程特有对象实例。
ThreadLocal实例也被称为线程局部变量(Thread-local Variable)。
ThreadLocal类的常用方法
方法 | 功能 |
---|---|
T get( ) | 获取与该线程局部变量关联的当前线程的线程特有对象 |
void set(T value) | 重新关联该线程局部变量所对应的当前线程的特有对象 |
T initialValue( ) | 该方法的返回值(对象)就是初始状态下该线程局部变量所对应的当前线程的线程特有对象 |
void remove( ) | 删除该线程局部变量与相应的当前线程的线程特有对象之间的关联关系 |
线程特有对象可能导致的问题
- 退化与数据错乱
- 可能导致内存泄漏、伪内存泄露
线程特有对象的典型应用场景
- 需要使用非线程安全对象,但又不希望因此而引入锁。
- 使用线程安全对象,但希望避免其使用的锁的开销和相关问题。
- 隐式参数传递。
- 特定于线程的单例模式。
需要使用非线程安全对象,但又不希望因此而引入锁
示例代码:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class ServletWithThreadLocal extends HttpServlet {
private static final long serialVersionUID = -9179908895742969397L;
// ThreadLocal实例通常会被作为某个类的静态字段使用
final static ThreadLocal<SimpleDateFormat> SDF = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final SimpleDateFormat sdf = SDF.get();
String strExpiryDate = req.getParameter("expirtyDate");
try (PrintWriter pwr = resp.getWriter()) {
sdf.parse(strExpiryDate);
// 省略其他代码
pwr.printf("[%s]expirtyDate:%s", Thread.currentThread().getName(), strExpiryDate);
} catch (ParseException e) {
throw new ServletException(e);
} // try结束
}
}
使用线程安全对象,但希望避免其使用的锁的开销和相关问题
示例代码:
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
// 避免多个线程共享SecureRandom实例可能导致的对ecureRandom内部所使用锁的争用,所以一个线程一个SecureRandom实例
public enum ThreadSpecificSecureRandom {
INSTANCE;
final static ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
SecureRandom srnd;
try {
srnd = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
srnd = new SecureRandom();
new RuntimeException("No SHA1PRNG available,defaults to new SecureRandom()", e)
.printStackTrace();
}
// 通过以下调用来初始化种子
srnd.nextBytes(new byte[20]);
return srnd;
}
};
/**
* 生成随机数
* @param upperBound
* @return
*/
public int nextInt(int upperBound) {
SecureRandom secureRnd = SECURE_RANDOM.get();
return secureRnd.nextInt(upperBound);
}
public void setSeed(long seed) {
SecureRandom secureRnd = SECURE_RANDOM.get();
secureRnd.setSeed(seed);
}
}
隐式参数传递
在类的方法中调用了类的实例域,这个被调用的实例域就是隐式参数。
隐式参数传递的实现通常是使用一个只包括静态方法的类或者单例类来封装对线程特有对象的访问,其他相应访问线程特有对象的代码只需要调用包装类的静态方法或者实例方法即可以访问线程特有对象。