为了确保线程的安全,通常需要保证可变的共享数据的同步访问,具体采用的方式有很多;但还有一种方法可以保证线程的安全,即是使可变数据不共享,或者是使数据不可变。所谓“线程封闭”即是仅在单线程中访问数据,也就是通过让可变数据不被多个线程共享以确保数据的正确性。
栈封闭
局部变量在函数的作用域当中,只有该函数本身可以访问,因此,也就自然地保证了线程的安全性。这就是线程封闭中的“栈封闭”。举例如下所示。
public int Test(int count){
int number = 0;
for(int i = 0; i < count; i++){
++number;
}
return number;
}
以上示例代码中的number就是一个栈封闭的变量,因为是Test函数的局部变量,因此该变量被封闭在单线程中,无需使用其他线程安全的关键字或方法,Java的语义天然地维护了其正确性。
ThreadLocal类
维持线程封闭性的一种更规范方法是使用ThreadLocal。这个类提供了ThreadLocal.get()、ThreadLocal.set()、ThreadLocal.initialValue()。initialValue()用于为ThreadLocal对象的共享变量赋予一个初始值,set()用于为ThreadLocal对象的共享变量赋值,get()用于获取ThreadLocal对象的共享变量。ThreadLocal对象的共享变量被封闭在对象内,只能被该对象操作。
ThreadLocal对象通常用于防止可变的单实例变量或全局变量进行共享。例如:在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的连接,如下为代码示例。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
//初始化ThreadLocal对象connectionHolder的共享变量为Connection类型的对象
public Connection initialValue(){
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection(){
//使用ThreadLocal对象的get()方法获取其共享变量
return connectionHolder.get();
}