JAVA |日常开发中异步线程详解
JAVA |日常开发中异步线程详解
,在日常的Java开发中,异步编程是一个非常重要的概念,特别是在处理I/O操作、网络请求、数据库访问等耗时任务时。异步编程能够使得主线程不被阻塞,从而提高应用的响应性和性能。本文将详细介绍Java中的异步线程编程,包括基本概念、常用方法以及最佳实践。
前言
在数字浪潮汹涌澎湃的时代,程序开发宛如一座神秘而宏伟的魔法城堡,矗立在科技的浩瀚星空中。代码的字符,似那闪烁的星辰,按照特定的轨迹与节奏,组合、交织、碰撞,即将开启一场奇妙且充满无限可能的创造之旅。当空白的文档界面如同深邃的宇宙等待探索,程序员们则化身无畏的星辰开拓者,指尖在键盘上轻舞,准备用智慧与逻辑编织出足以改变世界运行规则的程序画卷,在 0 和 1 的二进制世界里,镌刻下属于人类创新与突破的不朽印记。
一、异步线程概述
1.1 定义
在 Java 中,异步线程是指程序在执行过程中,启动一个或多个线程去执行某些任务,这些任务的执行和主线程(或调用线程)的执行是相互独立的。主线程不需要等待这些任务完成就可以继续执行其他操作。
例如,在一个 Web 应用中,当用户提交一个请求来查询数据时,服务器可以启动一个异步线程去数据库中查询数据,同时主线程可以继续处理其他用户的请求,而不是一直等待数据库查询完成。
1.2 优势
提高性能和响应速度:通过异步执行任务,可以让程序在执行耗时操作(如 I/O 操作、复杂计算等)的同时,继续处理其他任务,从而减少用户等待时间,提高系统的整体响应速度。
资源利用率提升:可以充分利用多核处理器的优势。当有多个异步线程时,它们可以在不同的 CPU 核心上同时运行,提高了 CPU 的利用率。
增强系统的并发处理能力:允许同时处理多个任务,对于处理高并发场景(如大量用户请求)非常有效。
二、创建异步线程的方式
2.1 继承Thread类
原理:通过定义一个类继承Thread类,并重写run方法。在run方法中定义需要异步执行的任务。然后创建该类的实例,并调用start方法来启动线程。
示例代码:
class MyThread extends Thread {
@Override
public void run() {
// 这里是异步执行的任务
System.out.println("异步线程开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步线程执行完毕");
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("主线程继续执行");
}
}
在上述代码中,MyThread类继承自Thread类,run方法中的代码就是异步执行的任务。在main方法中,创建MyThread实例并调用start方法启动线程后,主线程会继续执行,而不会等待MyThread中的任务完成。
2.2 实现Runnable接口
原理:定义一个类实现Runnable接口,实现run方法来定义任务。然后通过Thread类的构造函数将实现Runnable接口的实例传递进去,再调用start方法启动线程。
示例代码:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("异步线程开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步线程执行完毕");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println("主线程继续执行");
}
}
这种方式和继承Thread类的方式类似,但是实现Runnable接口更符合面向对象的设计原则,因为 Java 是单继承的,实现接口可以让一个类在继承其他类的同时实现线程任务。
2.3 使用Callable和Future(适用于有返回值的异步任务)
原理:Callable接口类似于Runnable接口,但是它可以返回一个结果并且可以抛出异常。Future接口用于获取Callable任务的执行结果。通过ExecutorService(如ThreadPoolExecutor)来提交Callable任务,ExecutorService会返回一个Future对象,通过Future对象可以获取任务的返回值或者检查任务是否完成等。
示例代码:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() {
System.out.println("异步线程开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步线程执行完毕");
return 10;
}
};
try {
Future<Integer> future = executorService.submit(callable);
System.out.println("主线程继续执行");
Integer result = future.get();
System.out.println("异步线程返回值: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
在这个例子中,Callable接口的call方法中定义了异步任务并且返回一个整数。通过ExecutorService提交Callable任务后,主线程可以继续执行。当需要获取结果时,通过Future对象的get方法来获取,get方法会阻塞主线程,直到任务完成并返回结果。
三、线程池与异步线程
3.1 线程池的概念
线程池是一种管理和复用线程的机制。它包含一组预先创建好的线程,这些线程可以被用来执行多个异步任务。当有新的任务到来时,线程池可以从池中分配一个线程来执行任务,任务执行完毕后,线程会被放回池中,等待下一个任务。
例如,想象一个工厂有固定数量的工人(线程),当有新的产品(任务)需要生产时,就分配一个工人去生产,工人完成工作后就回到等待状态,等待下一个产品的生产任务。
3.2 线程池的优势
降低资源消耗:避免了频繁创建和销毁线程所带来的资源消耗。创建线程需要分配系统资源(如内存),销毁线程也需要回收资源,线程池可以复用线程,减少这种开销。
提高响应速度:由于线程池中的线程是预先创建好的,当有任务需要执行时,可以立即从池中获取线程来执行任务,而不需要等待线程创建的时间。
便于线程管理:可以统一管理线程的数量、生命周期等。例如,可以设置线程池的最大线程数,防止过多的线程占用系统资源。
3.3 使用ExecutorService创建线程池并执行异步任务
示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
// 创建一个固定大小为3的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("正在执行任务 " + taskId);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
在这个例子中,首先使用Executors.newFixedThreadPool(3)创建了一个固定大小为 3 的线程池。然后通过循环提交了 10 个任务,这些任务会被线程池中的线程异步执行。当所有任务提交完成后,调用shutdown方法来关闭线程池。
四、异步线程的同步问题
4.1 共享资源竞争问题
当多个异步线程访问和修改共享资源(如全局变量、共享对象等)时,可能会出现数据不一致的问题。例如,两个线程同时对一个变量进行自增操作,可能会导致结果不符合预期。
例如,假设有一个全局变量count,两个异步线程都执行count++操作。如果没有适当的同步机制,可能会出现一个线程读取count的值后,还没来得及更新,另一个线程也读取了相同的值,导致最终count的值只增加了 1,而不是 2。
4.2 解决方法 - 使用synchronized关键字或Lock接口
synchronized关键字:可以用于修饰方法或者代码块。当一个线程进入被synchronized修饰的方法或代码块时,其他线程需要等待该线程执行完毕后才能进入。
示例代码(修饰方法):
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
final Counter counter = new Counter();
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 1000; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
counter.increment();
}
});
}
executorService.shutdown();
try {
// 等待线程池中的任务完成
executorService.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终计数: " + counter.getCount());
}
}
在这个例子中,Counter类中的increment方法被synchronized修饰,这样当多个线程调用这个方法来增加count的值时,就不会出现数据不一致的问题。
Lock接口(如ReentrantLock):Lock接口提供了比synchronized关键字更灵活的锁机制。可以通过lock方法获取锁,unlock方法释放锁。
示例代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class CounterWithLock {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
final CounterWithLock counter = new CounterWithLock();
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 1000; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
counter.increment();
}
});
}
executorService.shutdown();
try {
// 等待线程池中的任务完成
executorService.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终计数: " + counter.getCount());
}
}
这里CounterWithLock类使用ReentrantLock来保证increment方法在多个线程访问时的同步性。通过lock方法获取锁,在finally块中使用unlock方法释放锁,确保锁总是能够被正确释放。
结束语
亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。
愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。
至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。
优质源码分享
💞 关注博主 带你实现畅游前后端
🏰 大屏可视化 带你体验酷炫大屏
💯 神秘个人简介 带你体验不一样得介绍
🎀 酷炫邀请函 带你体验高大上得邀请
① 🉑提供云服务部署(有自己的阿里云);
② 🉑提供前端、后端、应用程序、H5、小程序、公众号等相关业务;
如🈶合作请联系我,期待您的联系。
注:本文撰写于CSDN平台,作者:xcLeigh(所有权归作者所有) ,https://blog.csdn.net/weixin_43151418,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。
亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(评论),博主看见后一定及时给您答复,💌💌💌
原文地址:https://blog.csdn.net/weixin_43151418/article/details/144187413(防止抄袭,原文地址不可删除)