目录
3. 并发共享
3.1. 并发问题
多个线程同时对共享资源进行修改时,有可能会发生读写操作的指令交错,导致结果跟预期不一样
3.1.1. 临界区
临界区(critical region):一段对共享资源的多线程并发读写操作的代码块
3.1.2. 竞态条件
竞态条件(Race Condition):多个线程代码在临界区执行,由于执行顺序不同而导致结果无法预测,例如数据丢失不一致等
解决方法:使用锁,同步工具,原子类等机制
3.1.3. 死锁
死锁的概念:多个线程由于资源竞争或通信问题导致的阻塞情况。例如:线程1持有线程2所需的资源,且线程1不释放,那么线程2就永远阻塞。
死锁的预防:
- 破坏互斥条件,允许资源共享
- 请求不到资源时,释放自己持有的资源
- 按序获取资源,避免循环等待
死锁的解决:
- 杀死持有资源的进程
- 操作系统强制释放资源
- 回滚进程
3.1.4. 活锁
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。
活锁的解决:
- 引入随机性,在检测到活锁时,暂停随机时间再重新尝试
- 设置重试次数,到达一定次数强制退出
- 活锁发生时主动干预,打破活锁
3.1.5. 饥饿
饥饿(Starvation):某个线程长期无法获取CPU时间片执行权而无法运行的情况,一般发生在字段调用不公平或优先级设置有问题的情况下
3.1.6. 内存一致性
内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决
3.2. 线程共享问题解决
有多种方法可以避免临界区中的竞态条件:
- 阻塞式-悲观锁:synchronized,Lock
- 非阻塞式-乐观锁:原子变量
3.3. 变量的线程安全分析
3.3.1. 成员变量和静态变量
成员变量和静态变量的线程安全分析要分两种情况:
- 不被共享时是线程安全的
- 被多个线程共享时,分为两种情况
- 读操作:是线程安全的
- 写操作:是线程不安全的
3.3.2. 局部变量
局部变量是否线程安全在于它是否逃逸出方法的作用范围
- 如果局部变量作用范围仅仅在当前方法,是线程安全的
- 如果局部变量作为参数传入或作为返回值返回,说明局部变量没有逃逸出方法的作用范围,是线程不安全的
- 局部变量仅有一个线程访问的时候是线程安全的
- private和final关键字是可以提供线程安全的,因为可以防止子类重写父类方法,其中可以开启另外一个线程访问局部变量造成线程不安全
3.4. 常见线程安全类
不可变类:内部属性是不可改变的,只能读不能写,所以是线程安全的
- String:
- String用final关键字修饰是为了防止子类去重写String方法导致线程不安全
- 其中的replace,substring改变了值,本质上是通过创建一个新的String对象来完成的,线程安全
- Integer
方法加上了synchronized方法的类:
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent包下的类,也称JUC
这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的