文章目录
- 1 并发和并行
- 2 进程和线程
- 2.1 进程与线程的区别
- 3 创建进程
- 3.1 在 Java 中创建进程
- 3.1.1 通过 Runtime 类的 exec() 方法
- 3.1.2 ProcessBuilder 创建进程
- 4 创建线程
- 4.1 继承Thead类
- 4.2 Runnable接口
- 4.3 使用Callable和Future
- 5 三种方式的对比
- 5.1 实现Runnable/Callable接口相比继承Thread类的优势
- 5.2 Callable和Runnable的区别
1 并发和并行
并行:指两个或多个时间在同一时刻发生(同时发生);
并发:指两个或多个事件在一个时间段内发生。
多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率;单核处理器,CPU时间片切换使用,并发执行程序。
2 进程和线程
进程:每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。
2.1 进程与线程的区别
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
进程之间不能共享内存,而线程之间可以共享内存。
3 创建进程
3.1 在 Java 中创建进程
在 windows 操作系统中,创建一个进程通常就是打开某个应用软件。
3.1.1 通过 Runtime 类的 exec() 方法
Runtime: 允许应用程序和应用正在运行的环境交互
Runtime run = Runtime.getRuntime();
//打开记事本
run.exec("notepad");
3.1.2 ProcessBuilder 创建进程
ProcessBuilder
用来创建一个操作系统进程。
public static void main(String[] args) throws IOException {
// 执行命令
ProcessBuilder pb = new ProcessBuilder("ls");
Process process = pb.start();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
}public static void main(String[] args) throws IOException {
ProcessBuilder pb = new ProcessBuilder("ls");
Process process = pb.start();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
// 打开应用程序
ProcessBuilder p = new ProcessBuilder("C:/Program Files/Notepad++/Notepad++.exe");
p.start();
}
4 创建线程
Java中创建线程主要有三种方式:
1.继承Thread类
2.实现Runnable接口
3.使用Callable和Future
4.1 继承Thead类
- 继承Thread类并重写run方法
- 创建线程对象
- 调用该线程对象的start()方法来启动线程
public class CreateThreadTest {
public static void main(String[] args) {
new ThreadTest().start();
}
}
class ThreadTest extends Thread{
@Override
public void run() {
}
}
4.2 Runnable接口
- 定义一个类实现Runnable接口,并重写该接口的run()方法
- 创建 Runnable实现类的对象,作为创建Thread对象的target参数,此Thread对象才是真正的线程对象
- 调用线程对象的start()方法来启动线程
public class CreateThreadTest {
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
new Thread(runnableTest, "线程名").start();
}
}
class RunnableTest implements Runnable{
@Override
public void run() {
}
}
注意:启动线程是调用
start()
方法,而不是调用run()
方法。
4.3 使用Callable和Future
Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值,可以声明抛出异常。Callable是一个函数式接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Future接口来接收Callable接口中call()方法的返回值。
使用Callable和Future创建线程的步骤如下:
- 定义一个类实现Callable接口,并重写call()方法,该call()方法将作为线程执行体,并且有返回值
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象
- 使用FutureTask对象作为Thread对象的target创建并启动线程
- 用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class CallableTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " is running");
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
try {
System.out.println("子线程的返回值: " + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName() + " is running: " + sum);
return sum;
}
}
5 三种方式的对比
5.1 实现Runnable/Callable接口相比继承Thread类的优势
- 适合多个线程进行资源共享
- 可以避免java中单继承的限制
- 增加程序的健壮性,代码和数据独立
- 线程池只能放入Runable或Callable接口实现类,不能直接放入继承Thread的类
5.2 Callable和Runnable的区别
- Callable重写的是call()方法,Runnable重写的方法是run()方法
- call()方法执行后可以有返回值,run()方法没有返回值
- call()方法可以抛出异常,run()方法不可以
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果
参考:
[Java 多线程详解(二)------如何创建进程和线程