并发编程复习
一、进程和线程
- 进程对应的是程序,每个进程对应一定的地址空间,暂停时保存当前的状态,为进程切换提供了可能
- 单核CPU的话任一时间只有一个进程在占用CPU
- 针对进程子任务只能串行的问题,出现了线程的概念。每个线程对应一个子任务。(为什么不多进程?分配进程成本高,进程切换消耗大,进程间资源不共享)
- 进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能。进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。线程共享进程的资源
二、Java内存模型
- 线程间共享变量存储在主内存中
- 每个线程都有私有的本地工作内存,本地内存中存储了共享变量的副本,线程对变量的读写操作都在本地进行。
三、并发编程需要考虑的问题
共享性
- 每个线程操作的始终是本地内存中的变量
- Servlet以单实例多线程的方式工作。只要Servlet中的代码只使用局部变量,Servlet就不会导致同步问题。
- Spring MVC的控制器也是这么做的,从请求中获得的对象都是以方法的参数传入而不是作为类的成员。
- Struts2的做法正好相反,因此Struts2中作为控制器的Action类都是每个请求对应一个实例
互斥性
- 加锁(共享锁、排他锁)
- 不变模式,用final修饰。Java中的Spring就是具有不变形的代表,所以是线程安全的
原子性
- volatile无法保证原子性
可见性
- volatile修饰的变量,更改后会立刻刷新到主内存中,其他线程也会进行同步
有序性
- 编译级别的重排序,比如编译器的优化
- 指令级重排序,比如CPU指令执行的重排序
- 内存系统的重排序,比如缓存和读写缓冲区导致的重排序
四、volatile和synchronized
实现原理:
- volatile:lock前缀指令实现内存屏障,防止指令重排,并且把指令直接更新到主存中
- synchronized:在JVM层次实现,使用monitor(
每个对象
都有一个监视器锁)来实现同步,其中同步代码块采用monitorenter、monitorexit指令显式实现,而同步方法则使用ACC_SYNCHRONIZED标记符隐式实现
修饰对象:
- volatile:修饰变量
- synchronized:修饰成员方法(锁该对象),修饰静态方法(锁该类的.Class对象),修饰代码块(可以指定需要哪个对象的锁)。
五、线程状态改变
- Object的方法:wait/notify/notifyAll
- Thread的方法:sleep/join/yield
- wait:释放锁,进入等待池
- join:使主线程等待子线程执行完成后再执行,换句话说就是将线程的并行执行变为串行执行
- yield:线程让出CPU,回到就绪状态