线程安全的解释是:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全
对于线程安全类的实例进行顺序或并发的一系列操作。都不会导致实例处于无效状态。
1.什么是无效状态
有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。 无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,是不变类,是线程安全的。
无状态线程永远都是线程安全的。例如:
public class StatelessServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int integer = 100; System.out.println("我不影响其他进程"); } }
当我们定义状态元素,一个全局变量,然后在service中进行操作,则不是线程安全的了。
public class StatelessServlet extends HttpServlet { private int count = 0; @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int integer = 100; count++; System.out.println("我不影响其他进程"); } }
当两个线程进行调用,实际应该等于12,但是可能会出现等于11
在处理的过程中,相对于在同一状态下的其他操作而言,必须是原子性或不可分割的,为了避免竞争条件,必须阻止其他线程访问我们正在修改的变量,保证在我们修改完成后再近些操作。
2.锁
内置锁机制:synchronized块(由锁对象的引用,锁保护的代码块组成),synchronized方法的锁(锁对象本身,如果是静态的synchronized方法,则从class对象上获取锁)
synchronized(lock){
访问或修改被锁保护的共享状态
}
内部锁扮演着互斥锁,执行现场进入synchronized块之前会自动获得锁,如java对象(可以隐式作为一个同步锁的角色)。当正常退出或从异常代码库中退出,都会自动释放锁。获得锁的唯一路径:进入该内部锁保护的同步块或方法。
建议不这样做(给service的方法加上同步锁)
public class StatelessServlet extends HttpServlet { private int count = 0; @Override protected synchronized void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int integer = 100; count++; System.out.println("我不影响其他进程"); } }
对于每一个涉及多个变量的不变约束,都需要同一个锁保护其所有变量。[自己理解:当我们需要多个变量修改,必须保证这多个变量要在同一个锁下面]
3.活跃度与性能
上面我们在service方法上加上了同步锁,但是这样会导致多个请求排队一个个进行接收处理,不能同步请求service方法,代价高昂,Service是处理多请求的,这样显然是不合理的,
解决办法:
缩小synchronized的范围来维护现场安全性,可容易提高线程安全性。
通常简单性与性能之间是相互牵制的,实现一个同步策略时,不要过早地为了性能而牺牲简单些,有些耗时的计算或操作,比如网络或控制台I/O,难以快速完成,执行这些操作期间不要占有锁。