进程和线程的区别
进程
- 定义:进程是程序在执行过程中的一个实例,它是操作系统分配资源(如内存、文件句柄等)和调度的基本单位。
- 结构:每个进程都有自己的地址空间,代码段、数据段和堆栈。在进程之间,地址空间是相互独立的,进程间的通信通常需要通过特定的机制(如管道、消息队列、共享内存等)。
- 状态:进程可以处于就绪、运行、阻塞等状态。操作系统负责调度这些进程,决定哪一个进程可以使用CPU。
- 实例:多数应用程序(如文本编辑器、网页浏览器等)可以同时有多个进程实例,而某些应用(如系统服务和某些限制性程序)则通常只有一个实例。
线程
- 定义:线程是进程中的一个执行单元,它是程序的最小执行单位。一个进程可以创建多个线程,这些线程共享进程的资源。
- 结构:每个线程拥有自己的堆栈和寄存器,但它们共享进程的地址空间,包括代码段、数据段和打开的文件等。
- 并发:因线程共享资源,因此在多线程程序中需要进行适当的同步,避免数据竞争和不一致的问题。常用的同步机制包括互斥锁(mutex)、信号量(semaphore)等。
- 调度:在线程级别,调度较快,因为线程之间的切换相对轻便(比进程切换开销小)。在Java中,线程是最小的调度单元,线程由操作系统调度运行。
并发和并行
单核CPU下,线程实际是串行执行的。操作系统的任务调度器,把CPU的时间片( windows下时间片最小约为15毫秒 )分给不同的程序使用,只是CPU在线程间的切换非常快,让人感觉好像是同时运行的。总结为:微观串行,宏观并行。
- 并行 (Parallelism):同一时刻有多个指令在多个处理器(CPU)或核心上同时执行。
- 并发 (Concurrency):同一时刻有多个指令在单个处理器(CPU)上交替执行。
Java线程
创建线程
Thread
- 线程启动需要调用 start()方法,如果线程直接调用 run() 方法,那么由主线程进行执行
- 当线程A调用 start() 方法时,Java 虚拟机会创建一个新的线程B并调用线程B的 run() 方法,这意味着,调用 start() 方法后,当前线程A和新线程B会同时运行。当前线程A是执行 start() 调用的线程,而新线程B则执行其 run() 方法
Thread 构造器:
- public Thread()
- public Thread(String name)
public class ThreadDemo {
// 创建线程对象
Thread t1 = new Thread("t1") {
@Override
// run 方法内实现了要执行的任务
public void run() {
log.debug("hello");
}
};
t1.start();
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
Runnable
- public Thread(Runnable target)
- public Thread(Runnable target, String name)
Thread代表线程,Runnable表示可运行的任务
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable的run():
使用 Runnable 的优点:
- 线程任务类只是实现了 Runnable 接口,可以继续继承其他类,避免了单继承的局限性
- 同一个线程任务对象可以被包装成多个线程对象
- 适合多个多个线程去共享同一个资源
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立
- 线程池可以放入实现 Runnable 或 Callable 线程任务对象
Callable
Callable是用于定义可返回结果的任务的接口,与Runnable接口相比,核心在于可返回结果和允许抛出异常
FutureTask 是 Runnable 和 Future 接口的实现类,用于包装 Callable 或 Runnable 任务,使其具备以下能力:
- 异步执行:作为 Runnable 被线程执行
- 结果管理:通过 Future 接口获取任务结果、取消任务、查询任务状态
- get() 阻塞获取任务结果,直到任务完成或抛出异常
(run() 执行完后会把结果设置到 FutureTask 的一个成员变量,get() 线程可以获取到该变量的值)
- get(long timeout, TimeUnit) 带超时的阻塞获取结果
// 1. 创建Callable任务
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 42;
};
// 2. 包装为FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(task);
// 3. 启动线程执行
new Thread(futureTask).start();
// 4. 获取结果(阻塞)
int result = futureTask.get();