当我们在多线程程序中操作一个数据时,保证此数据的线程安全是必须的。一般的,我们会将与此数据相关的操作同步化,在处理此数据附近创建临界区,通过类似串行的方式让多线程按序访问保证安全。除了这种同步的方法,还有其他的方法可以保证数据的安全性。
保证数据线程安全的思路基本有以下三种,这三种中又分别有不同的做法:
1.线程封闭技术,让数据只能被单个线程所见。
2.不变性,使用不可变对象。
3.同步技术,正确的发布并维护共享数据。
前面我们将对象设置成不可变对象,来保证对象在多线程间对象线程安全。其实就是我们躲避开了并发问题。另外一种躲避线程并发问题就是线程封闭。
线程封闭
线程封闭指的是让数据只能处于单个线程所见,在这种情况下,其他线程连看到看不到此数据,肯定不会发生线程安全问题。这个原理听起来简单,但实际上使用的范围很广泛。比如在JDBC中connection对象就是通过threadLocal线程封闭方法保证在多线程中的安全性。JDBC负责程序和数据库的连接,对于服务器而言同一个时候有多个客户端连接是很正常的事情,所以此处必须保证线程安全。最好的做法就是将connection对象封闭在客户端线程中,客户端线程使用完毕后将connection对象归还给服务器线程池,这是一个基本的线程封闭,在JDBC规范中也没有规定connection必须进行线程同步,这也证实了connection对象确实采用线程封闭这一做法。
通俗的说,线程封闭就是:让共享变量变为私有的局部变量,“让公交车变成私家车”。
常用的线程封闭有三种技术,分别是:
Ad-hoc线程封闭
一种“程序员根据自己理解临时控制的线程封闭变量”。总之,Ad-hoc线程封闭不会是一个重要的概念。我们理解意思就好。
栈封闭
在栈封闭中,只能通过局部变量才能访问对象。栈封闭(也被称为线程内部使用或者线程局部使用)比Ad-hoc线程封闭更易于维护,也更加健壮。Java语言确保了基本类型的局部变量始终封闭在线程内。简单的说,栈封闭的意思是将使用的变量尽量作为局部变量处理,防止线程安全问题的出现。
ThreadLocal
线程封闭最规范的方法是使用ThreadLocal类,这也是各种面试最常问的类之一。
现有两个线程A和B,它们都能访问到一个变量str_c。在不经过任何处理时,str_c的值可以被A和B两个线程访问和修改。使用了ThreadLocal后将str_c作为一个线程的局部变量,对于每一个线程,通过ThreadLocal方法得到的str_c的值是该线程的str_c的值,这个值不会被其他线程修改,其他线程只能修改他们自己的str_c的值。在并发的代码中的任何一点我们都能访问ThreadLocal而获取str_c的值,但是获取的值是该线程的值,其他线程的str_c在这个时候被“屏蔽”了,一般情况下我们看不见且访问不到。
在工作项目中举例。在springMVC框架中,我们在封装Controller时习惯于将request、response两个对象通过ThreadLocal封装,因为这两个对象在每个线程中的内容肯定不同,通过这种技术,可以确保在高并发情况下的安全性。在上文说的JDBC中的connection也是通过ThreadLocal封装的,让connection成为一个共享变量几乎是不可能的,那么多的客户端连接同一个connection,处理起来将非常棘手,ThreadLocal是唯一选择。