1. 线程
1-1进程 (Process)
定义
进程是操作系统中执行的一个程序实例。它包含了程序代码和它的活动。每个进程都有自己独立的内存空间和资源,如文件描述符、网络连接等。
进程就是正在运行中的程序(进程是驻留在内存中的)
特点
独立性:每个进程有自己独立的地址空间,相互之间不影响。
资源分配:进程之间不能直接共享内存,进程间通信(IPC)需要使用特定的机制,如管道、消息队列、共享内存等。
开销较大:创建、销毁和上下文切换(context switching)进程的开销较大,因为涉及到内存的分配和回收等操作。
应用场景
操作系统内核、多任务操作系统的任务调度。 各种独立运行的应用程序。
1-2 线程 (Thread)
定义
线程是进程中的一个执行单元,也被称为轻量级进程(Lightweight
Process)。一个进程可以包含多个线程,它们共享进程的资源和内存空间。
线程就是进程中的单个顺序控制流,也可以理解成是一条执行路径
单线程:一个进程中包含一个顺序控制流(一条执行路径)
多线程:一个进程中包含多个顺序控制流(多条执行路径)
特点
共享资源:同一进程内的线程共享内存空间和资源,可以直接访问进程的全局变量。
轻量级:线程的创建和销毁开销较小,线程之间的上下文切换也比进程快。
并发执行:线程使得程序可以并发执行,提高了程序的响应速度和利用多核处理器的能力。
应用场景
需要大量并发操作的应用,如Web服务器、数据库服务器等。 用户界面程序中的事件处理。
进程与线程的对比
特性 | 进程 | 线程 |
---|---|---|
内存空间 | 独立 | 共享 |
创建销毁速度 | 慢 | 快 |
通信方式 | 需要IPC机制 | 直接访问进程内存 |
崩溃影响 | 单个进程崩溃不影响其他进程 | 线程崩溃可能导致整个进程崩溃 |
使用场景 | 独立运行的应用程序、多任务操作系统 | 并发任务、需要高效利用多核处理器的应用 |
2 线程创建
线程常见的创建的方式有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口、使用线程池
2-1继承 Thread 类
步骤:
创建一个类,继承 Thread 类
重写 run() 方法
创建 Thread 类的子类的对象
通过子类对象调用 start() 方法
public class MyThread extends Thread {
/**
* 当线程执行时,重写run方法来定义线程的具体行为。
* 本方法中,线程将打印其名称和一个计数器,重复10次。
* 这种方式常用于展示线程的并发执行或循环控制在多线程环境下的行为。
*/
@Override
public void run() {
// 循环10次,打印线程名称和循环计数
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "--------------" + i);
}
}
}
public class Test01 {
/**
* 程序入口主方法。
* 创建两个MyThread线程实例,分别启动它们,然后在主线程中执行一个循环打印操作。
* 这里展示了多线程的使用,以及主线程与用户定义线程的并发执行。
*/
public static void main(String[] args) {
/* 创建MyThread类的两个实例对象 */
MyThread my= new MyThread();
MyThread my1= new MyThread();
/* 启动线程my和my1,它们将并发执行run方法中的代码 */
my.start();
my1.start();
/* 在主线程中执行循环打印操作 */
for (int i = 0; i < 20; i++){
System.out.println("main~~~~~~~~~~~"+i);
}
}
}
2-2 实现 Runnable 接口
步骤:
新建一个类,并实现 Runnable 接口,
重写 run() 方法 new 一个线程对象,并将其放入 Thread 类的构造方法中
public class MyRunnable implements Runnable{
/**
* 当线程执行时,重写run方法来定义线程的具体行为。
* 本方法中,线程将打印其名称和一个计数器,重复20次。
* 这种方式常用于示例代码中,以展示线程如何执行自定义任务。
*/
@Override
public void run()
{
// 循环20次,模拟一个持续的任务执行过程
for (int i = 0; i < 20; i++){
// 打印当前线程的名称和一个计数器,用于标识线程执行的进度
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class Test02 {
/**
* 程序的入口点。
* 创建一个实现了Runnable接口的MyRunnable实例,然后创建一个线程并指定MyRunnable实例为该线程的执行体。
* 主线程随后启动这个新线程并开始执行,同时主线程自身也在循环打印"main"字符串。
* 这段代码展示了如何在Java中创建和启动一个线程,以及如何在主线程和新线程之间进行并发执行。
*
* @param args 命令行参数,本程序中未使用。
*/
public static void main(String[] args) {
// 创建MyRunnable实例,用于后续在线程中执行任务。
MyRunnable my = new MyRunnable();
// 创建一个新线程,并将MyRunnable实例作为该线程的执行体,同时给线程命名为"aaa"。
Thread t1 = new Thread(my,"aaa");
// 启动线程t1,使其开始执行run方法中的代码。
t1.start();
// 主线程循环打印"main"字符串,展示与新线程的并发执行。
for (int i = 0; i < 20; i++){
System.out.println("main" + i);
}
}
}
2-3 实现Callable接口
步骤
第一步编写一个类实现Callable接口,重写call()方法
启动线程 创建Callable接口实现类对象
创建一个FutureTask对象, 传递Callable接口实现类对象,
FutureTask异步得到Callable执行结果, 提供get() FutureTask 实现Future接口( get())
实现Runnable接口 创建一个Thread对象, 把FutureTask对象传递给Thread, 调用start()启动线程
public class MyCallale implements Callable {
/**
* 计算从0到999中所有能被3整除的数字的和。
*
* @return 所有符合条件的数字的和。
* @throws Exception 如果在执行过程中出现异常。
*/
@Override
public Double call() throws Exception {
double sum = 0; // 初始化累加和为0
// 遍历从0到999的所有数字
for (int i = 0; i < 1000; i++) {
// 如果当前数字能被3整除,则累加到sum中
if (i % 3 == 0) {
sum += i;
}
}
return sum; // 返回累加和
}
}
public class Test03 {
/**
* 程序入口主方法。
* 使用FutureTask和线程来异步计算,并获取计算结果。
* @param args 命令行参数
* @throws ExecutionException 如果计算过程中抛出异常
* @throws InterruptedException 如果线程在等待计算结果时被中断
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个MyCallable实例,用于执行具体的计算任务
MyCallale my=new MyCallale();
// 使用MyCallable实例创建一个FutureTask,FutureTask用于包装计算任务并处理计算结果
FutureTask task = new FutureTask<>(my);
// 创建一个线程并传入FutureTask,线程将执行这个任务
Thread t=new Thread(task);
// 启动线程,开始执行任务
t.start();
// 等待任务执行完成,并获取计算结果
Object o=task.get();
// 输出计算结果
System.out.println(o);
}
}
Runable和Callable的区别?
1.Callable有返回值,而且抛出异常。而Runnable无返回值和异常抛出 2.Callable它是对Runnable的补充。