通过这篇博客你能学到什么:

编写线程安全的代码,本质上就管理状态的访问,而且通常是共享的、可变的状态.
状态:可以理解为对象的成员变量.
共享: 是指一个变量可以被多个线程访问
可变: 是指变量的值在生命周期内可以改变.
保证线程安全就是要在不可控制的并发访问中保护数据.
如果对象在多线程环境下无法保证线程安全,就会导致脏数据和其他不可预期的后果
在多线程编程中有一个原则:
无论何时,只要有对于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问**
Java中使用synchronized(同步)来确保线程安全.在synchronized(同步)块中的代码,可以保证在多线程环境下的原子性和可见性.
不要忽略同步的重要性,如果程序中忽略了必要的同步,可能看上去是可以运行,但是它仍然存在隐患,随时都可能崩溃.
在没有正确同步的情况下,如果多线程访问了同一变量(并且有线程会修改变量,如果是只读,它还是线程安全的),你的程序就存在隐患,有三种方法修复它:
1. 不要跨线程共享变量
2. 使状态变为不可变的
3. 在任何访问状态变量的时候使用同步
虽然可以用上述三类方法进行修改,但是会很麻烦、困难,所以一开始就将一个类设计成是线程安全的,比在后期重新修复它更容易
封装可以帮助你构建线程安全你的类,访问特定变量(状态)的代码越少,越容易确保使用恰当的同步,也越容易推断出访问一个变量所需的条件.总之,对程序的状态封装得越好,你的程序就越容易实现线程安全,同时有助于维护者保持这种线程安全性.
设计线程安全的类时,优秀的面向技术--封装、不可变性(final修饰的)以及明确的不变约束(可以理解为if-else)会给你提供诸多的帮助
虽然程序的响应速度很重要,但是正确性才是摆在首位的,你的程序跑的再快,结果是错的也没有任何意义,所以要先保证正确性然后再尝试去优化,这是一个很好的开发原则.
1 什么是线程安全性
一个类是线程安全的,是指在被多个线程访问时,类可以持续进行正确的行为.
对于线程安全类的实例(对象)进行顺序或并发的一系列操作,都不会导致实例处于无效状态.
线程安全的类封装了任何必要的同步,因此客户不需要自己提供.
2 一个无状态的(stateless)的servlet
public class StatelessServlet implements Servlet { @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { BigInteger i = extractFromRequest(servletRequest); BigInteger[] factors = factor(i); encodeIntoResponse(servletResponse,factors); } }
我们自定义的StatelessServlet是无状态对象(没有成员,变量保存数据),在方法体内声明的变量i和factors是本地变量,只有进入到这个方法的执行线程才能访问,变量在其他线程中不是共享的,线程访问无状态对象的方法,不会影响到其他线程访问该对象时的正确性,所以无状态对象是线程安全的.
这里有重要的概念要记好:无状态(成员变量)对象永远是线程安全的
3 原子性
在无状态对象中,加入一个状态元素,用来计数,在每次访问对象的方法时执行行自增操作.
public class StatelessServlet implements Servlet { private long count = 0; @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { BigInteger i = extractFromRequest(servletRequest); BigInteger[] factors = factor(i); count++; encodeIntoResponse(servletResponse,factors); }
在单线程的环境下运行很perfect,但是在多线程环境下它并不是线程安全的.为什么呢? 因为count++;并不是原子操作,它是由"读-改-写"三个操作组成的,读取count的值,+1,写入count的值,我们来想象一下,有两个线程同一时刻都执行到count++这一行,同时读取到一个数字比如9,都加1,都写入10,嗯 平白无故少了一计数.
现在我们明白了为什么自增操作不是线程安全的,现在我们来引入一个名词竞争条件.
4 竞争条件
**当计算的正确性依赖于运行时相关的时序或者多线程的交替时,会产生竞争条件**.
我对竞争条件的理解就是,**多个线程同时访问一段代码,因为顺序的问题,可能导致结果不正确,这就是竞争条件**.
除了上面的自增,还有一种常见的竞争条件--"检查再运行".
废话不多说,上代码.
/** * @author liuboren * @Title: RaceCondition * @ProjectName multithreading * @Description: TODO * @date 2018/10/7 15:54 */

