Java 并发编程技术
线程简介
什么是线程
进程:现代操作系统运行一个程序时,会为其创建一个进程。(进程可以认为是运行着的程序,活着的程序)。
线程:现代操作系统的最新调度单位就是线程,线程也称为轻量级进程(Light Weight Process)。一个进程中可以包含多个线程,这些线程有自己的计数器、堆栈、和局部变量属性,并且能够访问共享的内存变量。
为什么要使用多线程
使用多线程的原因主要有:
- 更多的处理器核心
- 一个线程在同一时刻只能运行在一个处理器上,将逻辑分配到多个核心处理器上更加有效率。
- 更快的响应时间
- 将数据一致性不强的操作分派给其他线程,使响应用户请求的线程尽快完成,缩短响应时间。
更好的编程模型
线程的优先级
在 Java 线程中, 通过一个整型成员变量 priority 来控制优先级,优先级的范围以 1~10 ,在线程构建的时候可以通过 setPriority(int) 方法来修改优先级,默认优先级是 5,优先级高的线程分配时间片的数量要多于优先级低的线程。
线程的优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会 Java 线程对于优先级的设定。
线程的状态
Java 线程在运行的生命周期中可能处于下表所示的 6 种不同的状态,在某一时刻,线程只能处于其中一个状态。
Daemon 线程
Daemon线程是一种支持性线程,主要是用在后台程序做一些后台调度与支持性工作。这意味着当JVM中没有非Daemon线程时,JVM将自动退出。
可以通过调用Thread.setDaemon(true)方法将线程设为Daemon线程。(注:该方法必须在start()或者run()方法前执行,也就是说必须在线程启动前执行)
Daemon线程被用作,完成支持性工作,但是在java虚拟机退出时,Daemon线程中的finally块并不一定会执行。
注:在构建Daemon时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。
启动和终止线程
构造线程
线程运行前需要先构造一个线程,下面是java.lang.Thread中对线程进行初始化的部分。
从这里也能看到父线程与子线程的关系:
父线程就是当前线程(开启多线程的线程),子线程会具有与父线程一致的优先级, 守护线程,线程组,还会有父线程的可继承ThreadLocal。还会分配给一个唯一的ID。
init()运行完毕,线程对象就初始化好了,在堆内存中等待运行。
启动线程
线程完成初始化后,调用start()方法就可以启动这个线程,线程start()的含义:当前线程同步告知JVM,只要线程规划器空闲,应立即启动调用start()方法的线程。
理解中断
中断:一个标识位属性,通过调用线程的interrupt()方法使其进入中断状态。
线程可以通过检查自身是否被中断来进行响应。
线程通过方法isInterrupted()来判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断进行复位。
过期的 suspend()、resume() 和 stop()
suspend()用于暂停线程、resume()用于恢复线程、stop()用于停止线程,这三个方法都过期了。
原因:suspend()会导致线程占用资源进入休眠状态,容易导致死锁。stop()不能保证线程资源的正确释放,一旦调用直接结束,可能会导致程序运行在不确定的状态。暂停恢复方法可以用后面的等待/通知机制完成。
线程间的通信
volatile 和 synchronized 关键字
关键字volatile可以修饰字段(成员变量),就是告知程序,任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。
注:过多地使用volatile是不必要的,因为它会降低程序执行的效率。(每一个线程都有自己的内存区域,从自己的内存区域对值操作肯定最快的,使用了volatile的话就会对共享内存进行操作,相比之下自然速率就慢了)
关键字synchronized:修饰代码块、方法、静态方法。实质上是对一个对象的监视器(monitor)的获取,而且这个获取过程是排他的,也就是说同一时刻只有一个线程获取由synchronized所保护对象的监视器。任何对象都有自己的监视器,当对象由同步块或者对象的同步方法调用时,执行方法的线程必须先获取对象的监视器才能进入同步块或者同步方法,而没有获取监视器的线程会阻塞在同步块与同步方法的入口,进入BLOCKED状态。