一、
并发(concurrency),简单来说,就是cpu在同一时刻执行多个任务。
而java并发是由多线程实现的;
在jvm的世界里,线程就像不相干的平行空间,串行在虚拟机中(当然线程之间是可以交互的,也并不一定是串行)
多线程的存在就是压榨cpu,提高程序性能,但同时存在线程安全问题。
死锁
和
脏数据
就是典型的线程安全问题。
简单来说,线程安全就是:
在多线程环境中,能永远保证程序的正确性。
只有
存在共享数据
时才需要考虑线程安全问题。(其中,
方法区
和
堆
就是主要的线程共享区域。)
当多线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的;一个很好的线程安全的例子是没有字段和引用的 Servlet 类,因为没有字段和引用所以是
无状态的。
二、状态
1.基本概念
:①
有状态
就是有数据存储功能,
有状态对象(stateful bean)
就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
②
无状态
就是一次操作,不能保存数据。
无状态对象(stateless bean)
就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
2.Spring中的有状态和无状态
无状态的bean适合单例模式,可以共享实例,提高性能;有状态的bean在多线程环境下不安全,适合用prototype模式,每次请求都会创建一个新的bean实例。
默认情况下,spring bean工厂的实例都是singleton单例模式,容器只存在一个共享的bean实例。
理解了两者的关系,那么scope选择的原则就容易多了。例如service层和dao层默认singleton就行,因为不需要状态信息,也就相当于immutable(不可变)类;但是struts2中的User等实体类,是有状态信息的,所以在多线程环境中不安全,所以struts2默认的实现是prototype实现模式。
三、多线程下的并发访问策略
1.java监视器模式:一直使用某一对象的锁来保护某状态;
2.线程安全委托:将类的线程安全性
委托给
某个或多个线程安全的
状态变量;
四、解决机制
1.加锁 :①锁能使其保护的代码以串行的方式被访问,当给一个复合操作加锁后,能使其成为原子操作。一种错误的思想是只给写数据加锁,其实对对数据进行操作的所有方法都需要加锁,不管是读还是写;②加锁时需要考虑性能问题,不能一味的给整个方法加锁synchronized就完事了,应该将方法中不影响共享状态的且执行时间较长的代码分离出去;③加锁的含义不仅仅局限于互斥,还包括可见性。为了确保所有线程都能看到最新值,读操作和写操作必须使用同样的锁对象。
2.不共享状态 :①无状态对象:无状态对象一定是线程安全的,因为不会影响其他线程;
②线程关闭 : 仅在单线程环境下使用。
3.不可变对象 :可以使用final修饰的对象保证线程安全,由于final修饰的引用型变量(除了String)不可变指的是引用不可变,但其指向的对象是可变。