java多线程的实现方式:
- 实现runnable接口
- 继承thread类
java程序运行的方式:
- 串行
- 并行
- 并发
多线程编程相关概念:
- 状态变量:即类的实例变量、静态变量
- 共享变量:可以被多个线程访问的变量。共享是指变量有可能被共享,而不是一定会被共享。状态变量也是共享变量
- 局部变量:方法内部定义的变量,非共享变量
java多线程面临的问题:
1、竞态
多线程编程式出现的,同样的输入,输出的结果有时正确有时不正确
竞态的两种模式:
- read-modify-read(读-改-写)
- check-then-act(检测而后行动)
2、线程安全
原子性
- 是针对共享变量操作而言
- 只有多线程环境下有意义
- 访问(读、写)某个共享变量的操作,从其执行线程以外的其它线程来看,该操作要么尚未发生,要么已经结束,即其它线程不能看到执行的中间状态
- 访问同一组共享变量的原子操作是不能被交错的
- 实现原子性的方式:使用锁(Lock),使用CAS(处理器指令,硬件锁)
- java语言中,除了long、double之外,其它基本数据类型都是原子性的操作
- java中所有变量的读操作都是原子性的
- volatile关键字可以实现变量的写操作的原子性
有序性
有序性是指,在某种情况下一个处理器上运行的线程所执行的内存访问操作,在另外一个处理器上运行的其它线程来说是乱序的。这涉及到重排序的概念。
- 可见性是有序性的基础
重排序
- 顺序结构是结构化编程的一种基本结构。
- 在多核处理器下,这种顺序结构会被打乱,处理器可能不会按照原本编程指定的顺序执行指令
- 一个处理器上执行的多个操作,从其他处理器的角度来看其顺序可能与目标代码所指定的顺序不一致。这种现象就叫作重排序(Reordering)。
- 重排序是对内存访问有关的操作(读、写)所做的优化,其会在不影响单线程程序正确的情况下提升性能。但是在多线程环境下,会产生不正确的结果,即导致线程安全问题。
- 重排序与可见性一样,不是必然出现的
术语定义:
- 源代码顺序( Source Code ):源代码中所指定的内存访问操作顺序。
- 程序顺序( Program Order ):在给定处理器上运行的目标代码( Object Code )所指定的内存访问操作顺序。尽管Java 虚拟机执行Java 代码有两种方式: 解释执行(被执行的是字节码Byte Code )和编译执行(被执行的是机器码) 。为便于讨论,这里仅将目标代码定义为字节码。
- 执行顺序( Execution Order ) :内存访问操作在给定处理器上的实际执行顺序。
- 感知顺序( Perceived Order ) : 给定处理器所感知到(看到) 的该处理器及其他处理器的内存访问操作发生的顺序。
重排序包括:指令重排序和存储子系统重排序
可见性
针对共享变量,多线程操作时,一个线程更新了共享变量,其它线程可能永远读取不到更新后的结果。
如果一个线程针对某个共享变量做了更新操作,其它线程能够获取到更新后的结果,即共享变量可见;否则不可见
处理器并不是直接操作主内存(ARM)而执行内存的读写操作,而是通过寄存器(register)、高速缓存(cache)、写缓存器(storeBuffer,又称writerBuffer)和无效化队列(invalidate queue),等部件实现内存的读、写操作。
volatile关键字可以保证可见性,内部运行逻辑:
提示JIT编译器被修饰的变量可能被多个线程共享,以阻止JIT编译器做出可能导致程序运行不正常的优化。(JIT编译器会将对未做明确多线程共享提示的变量执行优化,避免重复读取状态变量,进而导致在多线程环境下,状态变量不能被其它线程正确的读取)
多处理器情况下,处理器与处理器之间只能通过缓存同步实现变量状态共享,不能直接获取。
可见性,跟处理器无关,单处理器也存在可见性问题
3、上下文切换
上下文切换在一定程度上可以看做多个线程共享同一个处理器的产物
产生原因:
单处理器下的多线程的运行是基于时间片“time slice”分配的方式执行的。根据处理器、或者程序执行,处理器交替执行多线程程序的过程(一个线程由于自身暂停或停止被剥夺处理器使用权,被称为切出;一个线程被系统分配到处理器的使用权,成为切入),成为上下文切换。
分类:
- 自发性上下文切换:程序自我处理切出
- 非自发性上下文切换:由于线程调度器被强行切出
4、线程活性故障
由于资源稀缺,或者线程自身问题和缺陷的原因,导致线程一致处于非RUNNABLE状态;
线程处于RUNNABLE状态,但是要执行的任务一致无法处理。