Java 中的线程可以分成守护线程和用户线程, 用户线程会阻止 JVM 的正常停止, 只有当应用程序中的所有用户线程全部停止完毕的时候 JVM 才会正常停止; 相反, 守护线程则不会影响 JVM 的正常停止因此守护线程通常用于执行一些重要性不是很高的任务, 例如监视 JVM 中其他线程的执行状况
Java 中, 创建一个线程就是创建一个 Thread 类的实例 JVM 会为一个 Thread 实例分配两个调用栈所需的内存空间, 其中一个调用栈用于跟踪 Java 代码之间的调用关系, 另外一个调用栈用于跟踪 Java 代码对本地代码 (Native 代码主要是 C 代码) 的调用关系; 这两个调用栈也对应于两个线程, 一个是 JVM 中的线程(Java 线程), 另外一个是与 JVM 中的线程相对应的依赖于 JVM 宿主机操作系统的本地系统调用 Thread 实例的 start 方法来启动一个 Thread 实例, 之后当相应的线程被 JVM 的线程调度器调度到的时候相应的线程中的 run 方法执行
Java 中子线程是否是一个守护线程取决于父线程默认的情况下, 父线程是用户线程子线程也是用户线程, 父线程是守护线程则子线程也是守护线程此外可以通过调用 Thread 实例的 setDaemon 方法来修改线程的这一属性
Java 线程的状态可以通过调用相应的 Thread 类的实例的 getState()获取, 返回类型 Thread.State 是一个枚举类型 Java 线程的状态分成下面几种:
NEW: 一个刚创建而未启动的线程处于该状态, 每一个线程只会有一次处于该状态
RUNNABLE: 该状态可以看成是一个复合的状态其又包括两个子状态, READY: 表示处于该状态的线程可以被 JVM 的线程任务器调用其进行调度使之处于 RUNNING 状态, RUNNING 表示出于这个状态的线程正在运行调用 Thread 实例的 yield 方法可以使线程的状态由 RUNNING 转变成 READY
BLOCKED: 一个线程发起一个阻塞式 IO 操作之后, 或者视图去获得一个其他线程的持有的锁时, 相应的线程会处于 BLOCKED 状态
WAITING: 一个线程实例调用了 wait()join()park()等方法之后会处于这种等待其他线程执行的状态通过调用 notify()notifyAll()unpark()方法会使线程由 WAITING 状态转换成 RUNNABLE 状态
TIMED_WAITING: 此线程状态区别于 WAITING 状态在于该状态下的线程有一定的时间限制当超过指定的时间限制之后线程自动会由 WAITING 状态转换成 RUNNAED 状态
TERMINATED: 处于该状态的线程表示已经执行结束
下面介绍关于 JAVA 的内存模型
Java 所有变量都存储在主内存中
每个线程都有自己独立的工作内存, 里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝)
线程对共享变量的操作都是在自己的内存中完成, 而不是在主内存中完成
线程对共享变量的操作默认情况下在其他线程中不可见, 可以通过将本地线程的变量同步到共享内存中之后将共享变量同步到其他的线程
内存模型如下:
下面介绍在 Java 多线程编程中的几个常见特性: 原子性内存可见性和重排序
原子性: 原子 (Atomic) 操作指相应的操作是单一不可分割的操作
在多线程中, 非原子操作可能会受到其他线程的干扰, 使用关键字 synchronized 可以实现操作的原子性 synchronized 的本质是通过该关键字所包括的临界区的排他性保证在任何一个时刻只有一个线程能够执行临界区中的代码, 从而使的临界区中的代码实现了原子操作
内存可见性: CPU 在执行代码时, 为了减少变量访问的时间消耗会将代码中访问的变量值缓存到 CPU 的缓存区中, 代码在访问某个变量时, 相应的值会从缓存中读取而不是在主内存中读取; 同样的, 代码对被缓存过的变量的值的修改可能仅仅是写入缓存区而不是写回到内存中这样就导致一个线程对相同变量的修改无法同步到其他线程从而导致了内存的不可见性
可以使用 synchronized 或 volatile 来解决内存的不可见性问题两者又有点不同 synchronized 仍然是 通过将代码在临界区中对变量进行改变, 然后使得对稍后执行该临界区中代码的线程是可见的 volatile 不同之处在于, 一个线程对一个采用 volatile 关键字修饰的变量的值的更改对于其他使用该变量的线程总是可见的, 它是通过将变量的更改直接同步到主内存中, 同时其他线程缓存中的对应变量失效, 从而实现了变量的每次读取都是从主内存中读取
指令重排序: 编译器和 CPU 为了提高指令的执行效率, 经常会将指令进行重排序, 指令的重排序导致代码的执行顺序改变, 这经常会导致一系列的问题, 比如在对象的创建过程中, 指令的重排序使得我们得到了一个已经分配好的内存而对象的初始化并未完成, 从而导致空指针的异常 volatile 关键字可以禁止指令的重排序从而解决这类问题
总之, synchronized 可以保证在多线程中操作的原子性和内存可见性, 但是会引起上下文切换; 而 volatile 关键字仅能保证内存可见性, 但是可以禁止指令的重排序, 同时不会引起上下文切换
来源: https://juejin.im/post/5a8ebe20f265da4e777fbb0e