目录
2.3 实现Callable 重写call方法,配合FutureTask
一、基本概念
1.1线程与进程的区别
1.1.1 什么是进程
进程是指运行中的程序。 比如我们使用浏览器,需要启动这个程序,操作系统会给这个程序分配一定的资源(占用内存资源)。
1.1.2 什么是线程
线程是CPU调度的基本单位,每个线程执行的都是某一个进程的代码的某个片段(一个进程中可能包含多个线程)。
1.1.3 进程和线程的区别
- 根本不同:进程是操作系统分配的资源,而线程是CPU调度的基本单位。
- 资源方面:同一个进程下的线程共享进程中的一些资源。线程同时拥有自身的独立存储空间。进程之间的资源通常是独立的。
- 数量不同:进程一般指的就是一个进程。而线程是依附于某个进程的,而且一个进程中至少会有一个或多个线程。
- 开销不同:毕竟进程和线程不是一个级别的内容,线程的创建和终止的时间是比较短的。而且线程之间的切换比进程之间的切换速度要快很多。而且进程之间的通讯很麻烦,一般要借助内核才可以实现,而线程之间通讯,相当方面。
- .......
1.2 多线程
多线程:单个进程中同时运行多个线程。
多线程的作用:为了提高CPU的利用率。CPU的处理速度很快,处理任务时网络IO或磁盘IO需要等待,为避免等待CPU会在这期间调度其他线程。这样大大提高了程序的效率,提高用户体验。
多线程的局限
- 如果线程数量特别多,CPU在切换线程上下文时,会额外造成很大的消耗。
- 任务的拆分需要依赖业务场景,有一些异构化的任务,很难对任务拆分,还有很多业务并不是多线程处理更好。
- 线程安全问题:虽然多线程带来了一定的性能提升,但是再做一些操作时,多线程如果操作临界资源,可能会发生一些数据不一致的安全问题,甚至涉及到锁操作时,会造成死锁问题。
1.3 串行、并行、并发的区别
什么是串行:
串行就是一个一个排队,第一个做完,第二个才能上。
什么是并行:
并行就是同时进行处理。(一起上!!!)
什么是并发:
这里提到的并发不是高并发,是多线程的并发概念(CPU调度线程)。CPU在短时间内,切换执行线程,就像并行一样,但是只是CPU高速切换。
并行包括并发。并行就是多核CPU同时调度多个线程,是真正的多线程同时执行。
单核CPU只能实现并发,无法实现并行。
1.4 同步异步、阻塞非阻塞
同步与异步:执行某个方法后,被调用者是否主动反馈信息
阻塞和非阻塞:执行某个方法后,调用者是否一直等待结果
同步阻塞:用锅烧水,水开了,不会主动通知。烧水开始后,需要一直等待。
同步非阻塞:用锅烧水,水开了,不会主动通知你。烧水开始后,不需要一直等待,可以处理其他事情,但是需要时不时查看水开了没。
异步阻塞:用壶烧水,水开后,会主动通知你。烧水开始后,需要一直等待水烧开。
异步非阻塞:用壶烧水,水开后,会主题通知你。烧水开始后,不需要一种等待,可以去处理其他事情。
异步非阻塞是处理效果最好的,可以很好地提升效率地处理多线程任务。
二、线程的创建
线程的创建,有的人说三种,有的说五种等等,但是普遍认为是三种。
2.1 继承Thread类 重写run方法
启动线程调用start方法,会创建一个新的线程,并执行线程任务。
如果直接调用run方法,这样是使用当前线程执行run方法中的逻辑。
public class Test {
public static void main(String[] args) {
Job t1 = new Job();
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println("main:" + i);
}
}
}
class Job extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Job:" + i);
}
}
}
2.2 实现Runnable接口 重写run方法
public class Test {
public static void main(String[] args) {
IRunnable iRunnable = new IRunnable();
Thread t1 = new Thread(iRunnable);
t1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main:" + i);
}
}
}
class IRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("IRunnable:" + i);
}
}
}
常用使用方式:
- 匿名内部类方式:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("匿名内部类:" + i);
}
}
});
- lambda方式:
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("lambda:" + i);
}
});
2.3 实现Callable 重写call方法,配合FutureTask
Callable用于有返回结果的非阻塞的执行方法
同步非阻塞。
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1. 创建Callable
ICallable iCallable = new ICallable();
//2. 创建FutureTask,传入Callable
FutureTask futureTask = new FutureTask(iCallable );
//3. 创建Thread线程
Thread t1 = new Thread(futureTask);
//4. 启动线程
t1.start();
//5. 其他操作。。。
//6. 获取结果
Object count = futureTask.get();
System.out.println("总和为:" + count);
}
}
class ICallable implements Callable{
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 0; i < 100; i++) {
count += i;
}
return count;
}
}
2.4 基于线程池构建线程
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(10);
// 设置队列容量
executor.setQueueCapacity(20);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("MyExecutor-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
@Service
public class IService {
private final ThreadPoolTaskExecutor executor;
@Autowired
public IService (ThreadPoolTaskExecutor executor) {
this.executor = executor;
}
public void doSomething() {
executor.execute(() -> {
// 这里是你的任务代码
});
}
}
创建线程的方式有好几种,但是底层只有一种,实现Runnble