线程的内存模型
32 位操作系统的寻址空间为 2 的 32 次方, 也就是 4GB 的寻址空间; 系统在这 4GB 的空间里划分出 1GB 的空间给系统专用, 称作内核空间, 具有最高权限; 剩下 3GB 的空间为用户空间(一般 JVM 的可用内存最大只能是 2GB), 只能访问当前线程划分的内存地址. 用户线程需要访问硬件资源的时候需要委托内核线程进行访问, 这就涉及到 CPU 上下文在用户模式和内核模式的切换. 因此在使用线程或者进程的时候需要尽量避免不必要的用户模式和内核模式的切换.
进程是资源管理的最小单位, 线程是 CPU 调度的最小单位. 线程是比进程更轻量级的调度执行单位, 线程可以共享同一个进程内的资源(内存地址, 文件 I/O 等), 又是 CPI 独立调度的基本单位. 主要有三种实现线程的方法:
- #1 使用一个内核线程支持一个用户进程, 并且用户进程内支持多线程, 优势在于线程的切换都发生在用户模式, 劣势在于线程对于系统内核而言完全是透明的, 所有资源调度需要用户进程进行处理, 并且由于只有一个内核线程, 所以只有一个 CPU 资源可以使用(多核 CPU 完全排不上用场), 如果一个硬件 I/O 操作阻塞, 所以的线程的硬件 I/O 都被阻塞.
- #2 使用内核线程 (Kernel Level Thread) 一对一支持轻量级进程(Light Weight Process), 一个轻量级进程对应一个线程, 优势在于可以最大利用多 CPU 的性能, 多任务同时进行, 但最大劣势在于由于线程的阻塞或者唤醒都需要系统内核进行处理, 所以程序需要不断在用户模式和内核模式之间切换, 增加切换成本.
- #3 使用内核线程 (Kernel Level Thread) 一对一支持轻量级进程(Light Weight Process), 并使用轻量级进程多对多支持用户线程, 也就是混合使用前面两种实现的优势. 这样上下文切换主要发生在用户模式, 并且又可以依赖内核线程直接调用系统内核的功能.
线程的状态切换
Java 多线程主要涉及到两种数据访问的同步, 一种是 heap 中多线程可见的实例变量, 一种是 method area 中的类成员变量, 而 method call stack 中的数据是线程私有, 则不需要进行同步协调. 线程对象调用 start 方法之后就进入 Ready 状态等待线程调度器分配 CPU 资源进入 Running, 然后因为三种原因线程可能进入 waiting/blocked 状态: 调用 sleep/wait/suspend/join 方法, 调用了阻塞式 IO 方法, 等待获取锁.
线程的 Java 编程接口
JDK 自带开发包中支持两种线程的实现方式, extends Thread 和 implements Runnable, 两种方式都是通过重写 run()方法实现线程操作, 唯一的区别在于前者限制了当前类不能再有其他业务上的继承父类.
- public class App1 extends Thread {
- @Override
- public void run() {
- super.run();
- System.out.println(Thread.currentThread().getName() + ": start");
- }
- public static void main(String[] args) {
- App1 thread = new App1();
- thread.setName("App1 Thread");
- thread.start();
- System.out.println(Thread.currentThread().getName() + ": start");
- }
- }
- public class App1 implements Runnable {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + ": start");
- }
- public static void main(String[] args) {
- Thread app1Thread = new Thread(new App1());
- app1Thread.setName("App1 Thread");
- app1Thread.start();
- System.out.println(Thread.currentThread().getName() + ": start");
- }
- }
线程的启动方式:
- #1 调用 Thread.start()为异步实现, 表示通知线程规划器指定的线程已经就绪, 等待分配 CPU 资源并调用重写的 run()方法, 因此线程的执行具有不确定性;
- #2 调用 Thread.run()为同步实现, 将不会获取任何线程相关的特性, 而是简单的方法调用.
线程的退出方式:
- #1 Thread.run()执行结束后线程正常退出.
- #2 使用 Thread.stop()强制退出(不推荐)
- #3 使用 Thread.interrupt()向线程发送需要结束的信号, 但并不能立即停止线程, 线程代码中需要调用 Thread.interrupted()(或者是一个自定义的标志位)判断当前线程是否需要结束, 从而手动控制线程的执行状态.
线程的优先级
通过 Thread.setPriority(val)方法可以设置线程优先级, 范围从 1 到 10, 线程调度器会将 CPU 资源尽量分给具有高优先级的线程, 但并不意味着优先级高的线程总是能优先获取 CPU 资源; 优先级具有继承性, 子线程具有跟父线程同样的优先级.
死锁的产生和避免
Thread.suspend()和不指定超时时间的 Object.wait()最容易产生死锁. 利用 JDK 自带的工具 jps 可以监测线程是否已经进入死锁状态($ jps -l PID).
来源: http://www.bubuko.com/infodetail-2673374.html