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
关键字仅能保证内存可见性,但是可以禁止指令的重排序,同时不会引起上下文切换。