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(防止抄袭,原文地址不可删除)

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xcLeigh

万水千山总是情,打赏两块行不行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值