进程和线程

一个进程下包含多个线程,进程间通信复杂,需要用rpc,http等协议通信,线程间通信可以通过进程中的共享变量。

上下文切换

1. 暂停一个进程的处理,并将该进程的CPU状态(即上下文)存储在内存中的某个地方

2. 从内存中获取下一个进程的上下文,并在CPU的寄存器中恢复它

3. 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程/线程。

就CPU时间片而言,一次上下文切换大概需要5000~20000个时钟周期,相对一个简单指令几个乃至十几个左右的执行时钟周期,可以看出这个成本的巨大。

并发和并行

并发包含并行,并发更强调逻辑上同时发生,并行更强调物理上同时发生,并发包括了cpu单核宏观看同时运行的线程,微观上其实是cpu切换串行执行多任务的

线程的创建和启动

使用Thread类

Thread thread = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println(i);
    }
});
thread.start();

继承Runnable

Thread是线程,Runnable是任务,拆分开使用

Runnable runnable = () -> {
    for (int i = 0; i < 10; i++) {
        System.out.println(i);
    }
};

new Thread(runnable).start();

FutureTask

FutureTask<Integer> task = new FutureTask<>(() -> {
    System.out.println("hello");
    return 100;
});

new Thread(task).start();

Integer result = task.get();
System.out.println("结果是:" + result);

创建线程总体就一种,new Thread,但是里面是Callable和Runnable就分两种,Callable能返回结果,Runnable不能,Callable线程不支持接收作为参数,所以用FutureTask包装。

run和start

只有start方法才是启动线程,调用操作系统分配cpu,分到了cpu才调用run方法,手动调用run方法也能执行,但没有分到线程,run本质上就是一个方法。

重复调用一个线程的start方法会报错:

线程不是NEW的状态了,不让start。

线程的状态/生命周期

操作系统层面

  • 初始状态:语言层面创建了线程对象,还没和操作系统关联

  • 可运行状态:(准备就绪)随时可以运行任务

  • 运行状态:获取了cpu时间片,正在运行任务

  • 阻塞状态:因io而阻塞,让出cpu,直到被唤醒

  • 终止状态:线程执行完毕,进入终止状态,不会再转变其它状态

java语言层面

  • 初始状态(NEW):new了,但是没调用start

  • 运行状态(RUNNABLE):java把可运行和运行合二为一,都叫运行状态

  • 阻塞(BLOCKED):表示线程阻塞于锁

  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

  • 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

  • 终止(TERMINATED):表示该线程已经执行完毕。