一、并发和并行
1.并发和并行的概念
-
并发(Concurrent):指两个或多个事件在同一时间间隔内发生。在操作系统中,它通常意味着在一个时间段内有多个程序都处于已启动运行到运行完毕之间,且这些程序都是在同一个处理机上运行。然而,在任一时刻点上,只有一个程序在处理机上运行。这是通过任务调度器快速地在多个线程之间切换实现的,使得多个任务在宏观上看起来是同时进行的。
-
并行(Parallel):指两个或者多个事件在同一时刻同时发生。在操作系统中,它意味着每一个任务都被分配给每一个处理器独立完成,因此在同一时间点,多个任务是真正同时运行的。这通常发生在具有多个处理器的系统中,每个处理器都可以独立地执行任务,互不干扰。
2.cpu核数
- 定义:CPU核数指的是中央处理器内部的处理核心数量,也称作CPU的物理核心数。例如,一款CPU有4个物理核心,就意味着它内部有4个独立的处理核心,可以同时进行计算处理。
- 作用:每个物理核心都是一个独立的处理单元,能够执行计算机指令。核数的增加可以显著提高计算机的多任务处理能力。多核心处理器能够同时执行多个任务,从而缩短任务完成时间,提高系统的响应速度。
问题:单核心cpu,运行多线程程序会变成单线程的效果吗?
单核心CPU运行多线程程序时并不会完全变成单线程的效果,但性能的提升将受到限制。操作系统会使用线程调度器(Scheduler)来管理CPU资源的分配。当单核心CPU运行多线程程序时,调度器会不断地将CPU时间片分配给不同的线程,使它们交替执行。像这种逻辑上多个线程一起运行,实际上某个时刻只有一个线程在执行的现象就叫做并发。
假设CPU是多核心的,同一时刻可以有多个线程同时执行,这种现象就坐在并行。
所以,并发是程序上的逻辑概念,并行是物理概念。
二、创建线程
1.创建线程的三种方式
- 继承
Thread
类
通过继承Thread
类并重写其run()
方法,你可以创建一个新的线程。然后,你可以创建一个这个类的实例并调用其start()
方法来启动线程。start()
方法会调用run()
方法,但run()
方法本身并不会创建新的线程。
示例代码:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Running MyThread");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
- 实现
Runnable
接口
实现Runnable
接口并重写其run()
方法,然后你可以创建一个Thread
实例,将实现了Runnable
接口的类的对象作为参数传递给其构造函数。接着,你可以调用Thread
实例的start()
方法来启动线程。
示例代码:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Running MyRunnable");
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
这种方式通常更受欢迎,因为它支持多个线程共享一个资源或数据,即实现了Runnable接口的类的对象可以被多个线程共享。
- 实现
Callable
接口
从Java 5开始,你可以使用Callable
接口来创建一个线程。Callable
接口类似于Runnable
接口,但它有一个返回类型和一个可以抛出的异常列表。
import java.util.concurrent.*;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Result from MyCallable";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<String> task = new FutureTask(callable);
Thread thread = new Thread(task);
thread.start();
}
}
配合FutureTask
和ExecutorService
要使用Callable
接口,你可以将它与FutureTask
和ExecutorService
一起使用。
示例代码:
import java.util.concurrent.*;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Result from MyCallable";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(new MyCallable());
String result = future.get(); // 这会阻塞直到任务完成
System.out.println(result);
executorService.shutdown();
}
}
使用Callable
和FutureTask
的好处是,你可以获取任务的返回值,并且可以处理任务抛出的异常。此外,你还可以使用ExecutorService
来管理线程池,这使得创建和管理大量线程变得更加容易。
2.FutureTask的常用方法介绍
- 构造函数:
FutureTask(Callable<V> callable)
:使用给定的Callable对象创建新的FutureTask。Callable的call
方法会在任务执行时被调用。FutureTask(Runnable runnable, V result)
:使用给定的Runnable对象和结果值创建新的FutureTask。Runnable的run
方法会在任务执行时被调用,并且返回的结果将是构造函数中提供的result
。
- 执行任务:
- 由于
FutureTask
实现了Runnable
接口,因此可以直接将其传递给Thread
的构造函数来创建线程,并启动线程以执行任务。
- 由于
- 获取结果:
get()
:等待计算完成,然后获取其结果。如果在计算完成前调用此方法,则此方法将阻塞直到计算完成。如果计算过程中抛出异常,则此方法将抛出ExecutionException
。get(long timeout, TimeUnit unit)
:等待计算完成,但最多等待指定的超时时间。如果计算成功完成,则返回其结果。如果计算尚未完成,则最多等待指定的超时时间,然后返回null
(如果超时)或抛出TimeoutException
。如果计算过程中抛出异常,则此方法将抛出ExecutionException
。
- 检查任务状态:
boolean isCancelled()
:如果任务已完成(正常、异常或取消),则返回true
。boolean isDone()
:如果任务已完成(正常、异常或取消),则返回true
。boolean cancel(boolean mayInterruptIfRunning)
:尝试取消任务的执行。如果任务已经完成、已经取消或由于某种原因不能取消,则此方法将返回false
。如果成功取消任务,则此方法将返回true
。如果mayInterruptIfRunning
为true
,并且任务尚未开始,则此方法将尝试中断任务执行线程以停止任务。
- 异常处理:
- 如果在Callable的
call
方法或Runnable的run
方法中抛出了异常,并且任务已经被启动,那么这些异常将被封装在ExecutionException
中,并在调用get
方法时重新抛出。
- 如果在Callable的
通过使用FutureTask
,您可以方便地在线程中执行任务,并通过Future
接口获取任务的结果或处理异常。
演示:get()使用
import java.util.concurrent.*;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 这里可以添加一些模拟耗时操作
Thread.sleep(1000); // 模拟耗时1秒
return "Result from MyCallable";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable = new MyCallable();
FutureTask<String> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
// 等待任务完成并获取结果
try {
String result = task.get(); // get()方法会阻塞,直到任务完成
System.out.println(result);
} catch (InterruptedException e) {
// 如果当前线程在等待时被中断,将抛出此异常
Thread.currentThread().interrupt(); // 恢复中断状态
// 这里可以添加额外的错误处理逻辑
}
}
}