谈谈线程安全
并发环境下,有时会遇到对象共享问题。为了保证线程安全,通常有以下策略:
线程封闭
如果仅在单线程内访问数据,就不需要同步。这种技术被称之为“线程封闭”。它是实现线程安全的最简单的方式之一。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。
JavaWeb中有一个十分常见的例子:JDBC规范并不要求Connection对象是线程安全的。在典型的服务器应用程序中,线程从连接池中获得一个Connection对象,并且用该对象处理请求,使用完后再将对象归还给连接池。在Spring中,每个请求都是一个单独的线程,在Connection对象被返回之前,它不会被分配给其他线程。这就是线程封闭。
ThreadLocal
另一个线程封闭的例子是ThreadLocal。相信大家都很了解这个类了,每个线程内部有一个ThreadLocalMap,在这个map中储存了该对象的线程私有副本,ThreadLocal对象只是一把钥匙。其他线程获取不到,自然实现了线程封闭。
栈封闭
栈封闭听起来挺高大上,实际上是最简单的线程封闭实现。我们知道一个线程对应一个栈,只要我们在这个线程(也就是栈)中定义了一个基本类型变量,那么其他线程是访问不到这个变量的。自然就线程封闭了。对于引用类型(当然它也是基本类型),只要不说出去(不发布),其他线程也不知道它的存在,自然也就线程封闭了。
Ad-hoc线程封闭
好了,这下听起来更高大上了。实际上也非常简单,通俗来讲,就是在某个公共区域定义了一个非线程安全的对象,所有线程都持有这个对象的引用。这听起来非常不安全!但是,写操作只有某一个线程中有机会执行,其他线程只能读(为此,我们还得给它加上volatile关键字)。这样一来,也不会出现线程安全问题。
这听起来像某个十分冒失的程序员写出来的程序。因为它太脆弱了!要是哪天一拍脑袋忘掉了这个对象线程不安全,在多个线程中同时定义了写操作,那它一定会在某一天发生线程安全问题。维护线程封闭性的职责完全由程序实现来承担! 所以只有在简便性胜过脆弱性的时候才会考虑使用Ad-hoc线程封闭。
对象安全
只要对象安全,任凭几个线程随意折腾,都不会出现问题。
线程安全共享
线程安全的对象在内部实现同步。因此多个线程可以通过对象的公有接口来访问而不需要进一步的同步。比如ConcurrentHashMap。
只读共享
不可变对象和事实不可变对象就是典型的只读共享。只要一个对象是无状态的,那么就不存在多个线程同时“编辑”对象的问题,因为它根本就不支持“编辑”。例如String就是一个事实不可变对象,它对外部表现的和不可变对象别无二致。如果我们想要改变它,唯一的办法就是再new一个不可变对象,然后替换掉引用。这种方式是线程安全的,因为“替换引用”这一行为是原子的,某种意义上可以认为,创建对象的操作实际上被封装成了原子操作,是一种特殊实现的保护对象(又有点像事务提交)。而代价就是创建对象的消费较高。
保护对象
被保护的对象之恩那个通过特定的锁来访问,保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。