java并发-如何构建一个线程安全的程序
对于多线程程序来说,引发多线程安全问题的原因为同时访问共享资源导致的,因此设计一个多线程安全的应用程序关键在于两个方面,就是让两个原因不成立即可。所以对于多线程的并发问题只有两个解决方案,那就是让两个条件不成立,使变量不共享或者控制线程不要同时访问。对应到java中,就是线程封闭和访问控制两种方案。线程封闭是指将变量封闭到各个线程私有,使得变量不共享,从而达到多线程安全。访问控制则是通过锁,信号量等机制控制线程对于非私有变量的访问,达到多线程安全。
在java中实现线程安全的具体做法有三种
- 不可变(无法修改不存在线程安全问题)
- 线程封闭(ThreadLocal的set和get实现,使得变量不共享)
- 锁机制(访问控制,不同时访问)
对于实现线程安全来说,最简单和最实用的方法为线程封闭,通过调整程序的结构,使得变量尽量封闭在单个线程里,比如将状态变为方法的成员变量,使用ThreadLocal变量(每个线程一份副本)。
线程封闭
所谓线程封闭,就是利用封装将状态封装到线程中,使得该线程的状态对其他线程不可访问。具体比如线程方法的临时变量就是被封装到线程中了。线程封闭的状态必定是线程安全的,因为该状态不被其他线程共享,破坏了线程不安全的共享条件。
不可变
另外一个常见的控制线程安全的方法为使得变量不可变,也就是使用final关键字来修饰变量。一旦状态不可变也同样破环了线程不安全的条件。同时final变量还提高了程序的健壮性,使得调用方无法随意更改状态,防止了外部不大安全或者不合适的更改,当然final变量同时也消耗了一部分内存(较少),因此对于能够确定无需外部更改的变量,尽量使用final进行修饰。
锁机制
锁机制是当上述两种方案都无法实现线程安全后的另一种方法,java中提供了众多的锁机制。比如常见的syncronized关键字,ReentrantLock,CountDownLantch(闭锁),semaphore(信号量)和CyclicBarrier(栅栏)等,还有不常见的自旋锁,CAS操作等。所有的这些都是为了提供一种机制来控制线程对于共享状态的访问,使得每次对于共享状态的访问只有一个线程。