进程与线程
进程就是一个在内存中运行的应用程序。进程就是用来加载指令、管理内存、管理 IO 的,是资源分配的基本单位
线程是系统能够进行运算调度的最小单位,一个进程包含多个线程
二者对比
进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
进程拥有共享的资源,如内存空间等,供其内部的线程共享
进程间通信较为复杂
同一台计算机的进程通信称为 IPC(Inter-process communication)
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
并行与并发
并发(concurrent)是同一时间应对(dealing with)多件事情的能力
并行(parallel)是同一时间动手做(doing)多件事情的能力
同步和异步调用
需要等待结果返回,才能继续运行就是同步
不需要等待结果返回,就能继续运行就是异步
创建和运行线程
1.使用Thread方法
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
例如:
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
@Override
// run 方法内实现了要执行的任务
public void run() {
log.debug("hello");
}
};
t1.start();
2.使用 Runnable 配合 Thread
把【线程】和【任务】(要执行的代码)分开
Thread 代表线程
Runnable 可运行的任务(线程要执行的代码)
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
例如:
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
Java 8 以后可以使用 lambda 精简代码
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
import jdk.swing.interop.SwingInterOpUtils;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class ThreadTest01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread t1 = new Thread("t1") {
public void run() {
System.out.println("t1线程执行");
}
};
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("t2线程执行");
try {
t1.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2线程结束");
}
};
Runnable runnable1 = () -> System.out.println("t3线程执行");
FutureTask<Integer> futureTask = new FutureTask<Integer>(() -> {
System.out.println("t4线程执行");
return 100;
});
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable1,"t3");
Thread t4 = new Thread(futureTask,"t4");
t1.start();
t2.start();
t3.start();
t4.start();
t4.sleep(10000);//阻塞的是主线程
Integer result = futureTask.get();//不是t4.get()
try {
t1.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程执行");
System.out.println(result);
}
}
t1线程执行
t2线程执行
t3线程执行
t4线程执行
t2线程结束
main线程执行
100
Process finished with exit code 0
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
线程的 cpu 时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
start 与 run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();//程序仍在 main 线程运行, FileReader.read() 方法调用还是同步的
t1.start(); //程序在 t1 线程运行, FileReader.read() 方法调用是异步的
log.debug("do other things ...");
}
sleep 与 yield
sleep
1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
3. 睡眠结束后的线程未必会立刻得到执行
4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
5. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
6. 具体的实现依赖于操作系统的任务调度器
Runnable run1 = () -> {
int count = 0;
for(; ;){
System.out.println("t1线程--->" + count++);
}
};
Runnable run2 = () -> {
int count = 0;
for(; ;){
System.out.println("t2线程--->" + count++);
}
};
Thread t1 = new Thread(run1,"t1");
Thread t2 = new Thread(run2,"t2");
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();