答应被多个线程一起履行的代码称作线程安全的代码。线程安全的代码不包括竞态条件。当多个线程一起更新同享资源时会引发竞态条件。因而,了解Java线程履行时同享了什么资源很重要。

部分变量

部分变量存储在线程自己的栈中。也便是说,部分变量永久也不会被多个线程同享。所以,根底类型的部分变量是线程安全的。下面是根底类型的部分变量的一个比方:



部分的目标引证

目标的部分引证和根底类型的部分变量不太相同。虽然引证自身没有被同享,但引证所指的目标并没有存储在线程的栈内。一切的目标都存在同享堆中。假如在某个办法中创立的目标不会逃逸出(译者注:即该目标不会被其它办法取得,也不会被非部分变量引证到)该办法,那么它便是线程安全的。实际上,哪怕将这个目标作为参数传给其它办法,只需其他线程获取不到这个目标,那它仍是线程安全的。下面是一个线程安全的部分引证样例:



样例中LocalObject目标没有被办法回来,也没有被传递给someMethod()办法外的目标。每个履行someMethod()的线程都会创立自己的LocalObject目标,并赋值给localObject引证。因而,这儿的LocalObject是线程安全的。事实上,整个someMethod()都是线程安全的。即便将LocalObject作为参数传给同一个类的其它办法或其它类的办法时,它仍然是线程安全的。当然,假如LocalObject经过某些办法被传给了其他线程,那它就不再是线程安全的了。

目标成员

目标成员存储在堆上。假如两个线程一起更新同一个目标的同一个成员,那这个代码就不是线程安全的。下面是一个样例:



假如两个线程一起调用同一个NotThreadSafe实例上的add()办法,就会有竞态条件问题。例如



留意两个MyRunnable同享了同一个NotThreadSafe目标。因而,当它们调用add()办法时会形成竞态条件。

当然,假如这两个线程在不同的NotThreadSafe实例上调用call()办法,就不会导致竞态条件。下面是略微修改后的比方:



现在两个线程都有自己独自的NotThreadSafe目标,调用add()办法时就会互不搅扰,再也不会有竞态条件问题了。所以非线程安全的目标仍能够经过某种方法来消除竞态条件。

线程操控逃逸规矩

线程操控逃逸规矩能够协助你判别代码中对某些资源的拜访是否是线程安全的。

假如一个资源的创立,运用,毁掉都在同一个线程内完结,
且永久不会脱离该线程的操控,则该资源的运用便是线程安全的。

资源能够是目标,数组,文件,数据库衔接,套接字等等。Java中你无需自动毁掉目标,所以“毁掉”指不再有引证指向目标。

即便目标自身线程安全,但假如该目标中包括其他资源(文件,数据库衔接),整个使用或许就不再是线程安全的了。比方2个线程都创立了各自的数据库衔接,每个衔接自身是线程安全的,但它们所衔接到的同一个数据库或许不是线程安全的。比方,2个线程履行如下代码:

查看记载X是否存在,假如不存在,刺进X

假如两个线程一起履行,并且可巧查看的是同一个记载,那么两个线程终究或许都刺进了记载:

线程1查看记载X是否存在。查看成果:不存在
线程2查看记载X是否存在。查看成果:不存在
线程1刺进记载X
线程2刺进记载X

相同的问题也会发生在文件或其他同享资源上。因而,区别某个线程操控的目标是资源自身,仍是只是到某个资源的引证很重要。

推荐阅读