程序由指令和数据组成。但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备,而进程就是用来加载指令、管理内存、管理IO的
程序和进程 当一个程序被运行,从磁盘加载这个程序的代码至内存。这时,就开启了一个进程。进程(动态)可以视为程序(静态)的一个实例。大部分程序可以同时运行多个实例进程。如:记事本、浏览器;也有的程序只能启动一个实例进程。如:任务管理器
一个进程可以分为多个线程,而一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。
在Java中,线程作为最小调度单位,进程作为资源分配的最小单位。
在Java程序启动时,都会创建一个线程----Main线程。
如果你想在Main线程之外创建其他线程,可以用如下方法。
ThreadRunnable接口FutureTaskJava8 以后可以使用 Lambda 表达式来精简代码。如:
Runnable r = () -> { // 要执行的任务 } // 创建线程对象 Thread t1 = new Thread(r, "t1"); // 启动线程 t1.start();如果对 Lambda 表达式不了解的,可以看看这篇博客 手把手地带你走进Lambda表达式之门
FutureTask 能够接收 Callable 接口的参数,用来处理有返回结果的情况。
FutureTask<Integer> task = new FutureTask<>(() -> { // 要执行的任务 return 666; }) new Thread(task, "t1").start(); // 主线程阻塞,同步等待 task 执行完毕的结果 Integer result = task.get();查看 Thread 源码:
Thread 实现了 Runnable 接口Thread 有一个 Runnable 类型的成员变量 target查看 Thread 的构造方法:
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }最终会调用:
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ... this.target = target; ... }将构造方法中的参数传递给了成员变量 target。
查看 Thread 中的 run() 方法:
public void run() { if (target != null) { target.run(); } }如果 target != null,则运行 Runnable 接口中的 run() 方法;否则,直接运行Thread 中的 run() 方法。
方法1 中的原理: 通过 匿名内部类实现的方式可以看做是 Thread 的子类(亦或继承 Thread),子类重写了父类中的 run() 方法。所以,最终运行的是子类中的方法
方法2 中的原理: 把 Runnable 类型的变量作为 Thread 的构造方法的参数,在此构造方法中,将此参数传递给成员变量 target,所以 target != null,最终会执行 Runnable 接口中的方法,而上述的 Lambda 表达式将 Runnable 接口中的 run() 方法进行重写,所以,最终会执行 Lambda表达式。
JVM是由堆、栈、方法区等组成,其中,栈内存是给线程用的,每个线程启动后,JVM会为它分配一个栈内存。而每个栈是由多个栈帧组成,对应着每次方法调用时所占用的内存
因为以下原因导致CPU不再执行当前的线程,转而执行另一个线程的代码:
线程的CPU时间片用完垃圾回收有更优先级的线程需要执行线程自己调用了sleep()、wait()、lock() 方法等start() 方法表示启动一个新的线程,run() 方法表示线程启动后要执行的代码,那能否能直接调用 run() 方法?
public static void main(String[] args) { Thread t1 = new Thread() { @Override public void run () { System.out.println("hello world" + "=====" +Thread.currentThread().getName()); } }; t1.run(); }run() 方法还是可以执行,但并没有创建新的线程,还是在 main 线程中执行的
看下这段代码,打印 i 是什么?
static int i = 1; private static void test() throws Exception{ Thread t1 = new Thread(() -> { try { Thread.sleep(1000); i = 100; } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); System.out.println("i = " + i); } public static void main(String[] args) throws Exception{ test(); }流程分析:main 线程启动,调用 test() 方法,开启一个新的线程 t1,main 和 t1 线程都是在同时运行的,main 线程会先执行语句 “System.out.println("i = " + i);”,只有当 t1 线程休眠1s后,才会将 i 赋值为 100。
那么,如何让打印的 i 为100呢? 只需要添加一个 join() 方法
t1.start(); t1.join(); System.out.println("i = " + i);当 main 线程 执行到语句 “t1.join()”时,main 线程会一直等待着 t1 线程(t1 调用了 join()方法),直到 t1 线程运行结束后,main 线程才会继续执行。
打断 sleep、wait、join 的线程会清空打断标记。以 sleep 为例
public static void main(String[] args) throws Exception{ Thread t1 = new Thread(() -> { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); Thread.sleep(100); t1.interrupt(); System.out.println("打断标记:" + t1.isInterrupted()); }打断运行的线程是不会清除标记的
两阶段终止模式 在线程 t1 中如何优雅地终止线程 t2?
先看看两阶段终止模式的一个应用场景----做一个系统的健康监控,如:定时地去监控CPU 的使用率、内存的使用率等。可以使用一个后台的监控线程每隔2s不断地进行记录即可,当点击停止按钮时便不再监控了。
代码实现:
public class TwoPhraseTermination { // 监控线程 private Thread monitor; // 启动监控线程 public void start() { monitor = new Thread(() -> { while (true) { Thread current = Thread.currentThread(); if (current.isInterrupted()) { System.out.println("料理后事"); break; } try { Thread.sleep(2000); System.out.println("执行监控记录"); } catch (InterruptedException e) { // 重新设置打断标记 current.interrupt(); } } }); monitor.start(); } public void stop() { monitor.interrupt(); } public static void main(String[] args) throws Exception{ TwoPhraseTermination termination = new TwoPhraseTermination(); termination.start(); Thread.sleep(5000); termination.stop(); } }这就是实现了在一个 main 线程中优雅地终止了另一个线程。