定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为(大多数场景下,会弱化这个定义,限定为“单次调用”)都可以获得正确的结果,那么这个对象是线程安全的。
13.2.1 Java语言中的线程安全
线程安全并不是只有两种选项,安全或是不安全,而可以按照“安全程度”由强至弱,把Java语言中操作共享数据分为以下5类:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立。
1. 不可变
JDK1.5之后,不可变(Immutable)的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线程安全保障措施。
在Java语言中,如果共享数据是一个基本数据类型,那么只要使用final关键字修饰就可以保证它是不可变的。如果共享数据是一个对象,那就要保证对象的行为不会对其状态产生任何影响才行(如java.lang.String类的对象就是典型的不可变对象,我们调用它的substring()、replace()和concat()这些方法时都不会影响它原来的值,只会返回一个新构造的字符串对象)。
保证对象行为不影响自己状态的最简单途径就是把对象中带有状态的变量都声明为final。
Java API中符合不可变要求的类型除了String外,还有枚举类型,以及java.lang.Number的部分子类。
2. 绝对线程安全
绝对的线程安全完全满足上述线程安全的定义,即一个类要达到“不管运行时环境如何,调用者都不需要任何额外的同步措施”。在Java API中标注自己是线程安全的类,大多都不是绝对的线程安全,可以看以下关于java.util.Vector的例子。
一般来说,java.util.Vector就是线程安全的,因为它的add()、get()和size()这类方法都是被synchronized修饰的,但是即使它所有的方法都被修饰成同步,也不意味着在调用它的时候就不需要用同步手段了。
public class VoctorTest { private static Vector<Integer> vector = new Vector<Integer>(); public static void main(String[] args) { while (true) { for (int i = 0; i < 10; i++) { vector.add(i); } Thread removeThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < vector.size(); i++) { vector.remove(i); } } }); Thread printThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < vector.size(); i++) { System.out.println(vector.get(i)); } } }); removeThread.start(); printThread.start(); //不要同时产生过多的线程,否则会导致操作系统假死 while (Thread.activeCount() > 20) ; } } }
以上代码会报java.lang.ArrayIndexOutOfBoundsException错误,原因在于如果一个线程恰好在错误的时间里删除了一个元素,导致序号i不再可用,再用i访问数组就会抛出一个ArrayIndexOutOfBoundsException错误。要保证这段代码能正确执行,就要改成以下: