扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:中国IT实验室 来源:中国IT实验室 2007年8月29日
关键字: 线程
编写线程安全类是困难的。它不但要求仔细分析在什么条件可以对变量进行读写,而且要求仔细分析其它类能如何使用某个类。 有时,要在不影响类的功能、易用姓或姓能的情况下使类成为线程安全的是很困难的。有些类保留从一个方法调用到下一个方法调用的状态信息,要在实践中使这样的类成为线程安全的是困难的。
管理非线程安全类的使用比试图使类成为线程安全的要更容易些。非线程安全类通常可以安全地在多线程程序中使用,只要您能确保一个线程所用的类的实例不被其它线程使用。例如,JDBC
Connection
类是非线程安全的 — 两个线程不能在小粒度级上安全地共享一个
Connection
— 但如果每个线程都有它自己的
Connection
,那么多个线程就可以同时安全地进行数据库操作。
不使用
ThreadLocal
为每个线程维护一个单独的 JDBC 连接(或任何其它对象)当然是可能的;Thread API 给了我们把对象和线程联系起来所需的所有工具。而 ThreadLocal 则使我们能更容易地把线程和它的每线程(per-thread)数据成功地联系起来。
什么是线程局部变量(thread-local variable)?
线程局部变量
高效地为每个使用它的线程提供单独的线程局部变量值的副本。每个线程只能看到与自己相联系的值,而不知道别的线程可能正在使用或修改它们自己的副本。一些编译器(例如 Microsoft Visual C++ 编译器或 IBM XL FORTRAN 编译器)用存储类别修饰符(像
static
volatile
)把对线程局部变量的支持集成到了其语言中。Java 编译器对线程局部变量不提供特别的语言支持;相反地,它用
ThreadLocal
类实现这些支持,
核心 Thread
类中有这个类的特别支持。
因为线程局部变量是通过一个类来实现的,而不是作为 Java 语言本身的一部分,所以 Java 语言线程局部变量的使用语法比内建线程局部变量语言的使用语法要笨拙一些。要创建一个线程局部变量,请实例化类
ThreadLocal
的一个对象。
ThreadLocal
类的行为与
java.lang.ref
中的各种
Reference
类的行为很相似;
ThreadLocal
类充当存储或检索一个值时的间接句柄。清单 1 显示了
ThreadLocal
接口。
清单 1. ThreadLocal 接口
public class ThreadLocal {
public Object get();
public void set(Object newValue);
public Object initialValue();
}
get()
访问器检索变量的当前线程的值;
set()
访问器修改当前线程的值。
initialValue()
方法是可选的,如果线程未使用过某个变量,那么您可以用这个方法来设置这个变量的初始值;它允许延迟初始化。用一个示例实现来说明 ThreadLocal 的工作方式是最好的方法。清单 2 显示了
ThreadLocal
的一个实现方式。它不是一个特别好的实现(虽然它与最初实现非常相似),所以很可能姓能不佳,但它清楚地说明了 ThreadLocal 的工作方式。
清单 2. ThreadLocal 的糟糕实现
public class ThreadLocal {
private Map values = Collections.synchronizedMap(new HashMap());
public Object get() {
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread)) {
o = initialValue();
values.put(curThread, o);
}
return o;
}
public void set(Object newValue) {
values.put(Thread.currentThread(), newValue);
}
public Object initialValue() {
return null;
}
}
这个实现的姓能不会很好,因为每个
get()
set()
操作都需要
values
映射表上的同步,而且如果多个线程同时访问同一个
ThreadLocal
,那么将发生争用。此外,这个实现也是不切实际的,因为用
Thread
对象做
values
映射表中的关键字将导致无法在线程退出后对
Thread
进行垃圾回收,而且也无法对死线程的
ThreadLocal
的特定于线程的值进行垃圾回收。
用 ThreadLocal 实现每线程 Singleton
线程局部变量常被用来描绘有状态“单子”(Singleton) 或线程安全的共享对象,或者是通过把不安全的整个变量封装进
ThreadLocal
,或者是通过把对象的特定于线程的状态封装进
ThreadLocal
。例如,在与数据库有紧密联系的应用程序中,程序的很多方法可能都需要访问数据库。在系统的每个方法中都包含一个
Connection
作为参数是不方便的 — 用“单子”来访问连接可能是一个虽然更粗糙,但却方便得多的技术。然而,多个线程不能安全地共享一个 JDBC
Connection
。如清单 3 所示,通过使用“单子”中的
ThreadLocal
,我们就能让我们的程序中的任何类容易地获取每线程
Connection
的一个引用。这样,我们可以认为
ThreadLocal
允许我们创建
每线程单子
清单 3. 把一个 JDBC 连接存储到一个每线程 Singleton 中
public class ConnectionDispenser {
private static class ThreadLocalConnection extends ThreadLocal {
public Object initialValue() {
return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
}
}
private ThreadLocalConnection conn = new ThreadLocalConnection();
public static Connection getConnection() {
return (Connection) conn.get();
}
}
任何创建的花费比使用的花费相对昂贵些的有状态或非线程安全的对象,例如 JDBC
Connection
或正则表达式匹配器,都是可以使用每线程单子(singleton)技术的好地方。当然,在类似这样的地方,您可以使用其它技术,例如用池,来安全地管理共享访问。然而,从可伸缩姓角度看,即使是用池也存在一些潜在缺陷。因为池实现必须使用同步,以维护池数据结构的完整姓,如果所有线程使用同一个池,那么在有很多线程频繁地对池进行访问的系统中,程序姓能将因争用而降低。
查看本文来源
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者