Java多线程编程:实现并发处理的高效利器

Java多线程编程:实现并发处理的高效利器

作者:Stevedash

发表于:2023年8月13日 20点45分

来源:Java 多线程编程 | 菜鸟教程 (runoob.com)

​ 在计算机领域,多线程编程是一项重要的技术,可以使程序同时执行多个任务,充分利用计算资源,提高系统的性能和响应能力。Java作为一门广泛应用的编程语言,提供了丰富的多线程编程支持,使得开发人员可以轻松实现并发处理。本篇博客将向您介绍Java多线程编程的基本概念、创建线程的方式、线程同步和线程通信等内容,同时还会涉及到线程的生命周期、线程的优先级和线程的常用方法。

多线程编程的优势

​ 多线程编程在提高程序性能、资源利用率和响应速度方面具有明显的优势。通过充分利用多核处理器,可以在同一时刻处理多个任务,提高系统的整体吞吐量。此外,多线程还可以用于实现一些需要并发执行的场景,如并发请求处理、数据采集、实时计算等。

创建线程的方式

Java多线程编程有三种常见的方式来创建线程:
  1. 继承Thread类: 创建一个类继承Thread类,并重写run()方法来定义线程要执行的任务。

  2. 实现Runnable接口: 创建一个实现Runnable接口的类,并实现run()方法,然后通过Thread类的构造方法创建线程对象。

  3. 实现Callable接口: 创建一个实现Callable接口的类,并实现call()方法,可以获取线程执行后的返回值。

​ 每种方式都有其特点,选择合适的方式取决于具体需求。使用继承Thread类的方式编写简单,但由于Java不支持多重继承,可能限制了其他类的继承。而实现Runnable或Callable接口的方式可以更灵活地管理线程。


线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。img

线程的生命周期可以分为以下几个阶段:

  1. 新建状态(New): 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  2. 就绪状态(Runnable): 当调用线程的start()方法后,线程进入就绪状态,就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度,等待获取CPU时间片执行。
  3. 运行状态(Running): 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  4. 阻塞状态(Blocked): **如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。**在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  5. 死亡状态:(Terminated): 一个运行状态的线程完成任务或者其他终止条件发生时,又或者当线程的run()方法执行完毕,线程进入终止状态。

线程的优先级

每个线程都有一个优先级,用于指示线程在竞争CPU时间片时的优先顺序。线程的优先级分为1到10,其中1为最低优先级,10为最高优先级。可以使用setPriority()方法设置线程的优先级,但并不能保证优先级高的线程一定会先执行。**默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。**具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。


线程的常用方法

Java提供了一些常用的线程方法,用于管理线程的执行和状态:
  • start(): 启动线程,使其进入就绪状态。

  • join(): 等待线程执行完毕。

  • sleep(): 使线程休眠一段时间。

  • yield(): 让出CPU时间片,让其他线程执行。

  • isAlive(): 判断线程是否还存活。

示例代码

以下是一个简单的Java程序,演示了如何创建并启动多个线程,并使用线程的优先级和常用方法:
public class MultiThreadDemo {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable("Thread 1"));
        Thread thread2 = new Thread(new MyRunnable("Thread 2"));

        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MIN_PRIORITY);

        thread1.start();
        thread2.start();

        try {
            thread1.join(); // 等待thread1执行完毕
            thread2.join(); // 等待thread2执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程结束");
    }

    
    //实现Runnable接口创建线程
    static class MyRunnable implements Runnable {
        private String name;

        public MyRunnable(String name) {
            this.name = name;
        }
        
    //通过Callable接口和Future创建线程
    static class MyCallable implements Callable<Integer> {
        private String name;

        public MyCallable(String name) {
            this.name = name;
        }
        @Override
        public Integer call() throws Exception{
            //...
            return Integer;
        }
      
    //继承Thread类创建线程  三选一
    static class MyThread extends Thread{
         private String name;

        public MyCallable(String name) {
            this.name = name;
        }
    }
         //必须要重写run方法
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                System.out.println(name + ": " + i);
                try {
                    Thread.sleep(1000); // 休眠1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

下面是Thread 方法

下表列出了Thread类的一些重要方法:
序号方法描述
1public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4public final void setPriority(int priority) 更改线程的优先级。
5public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7public void interrupt() 中断线程。
8public final boolean isAlive() 测试线程是否处于活动状态。
上述方法是被 Thread 对象调用的,下面表格的方法是 Thread 类的静态方法。
序号方法描述
1public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
2public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
5public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

重点了解一下SetDeamon()标记成守护线程或者用户线程

基本概念:

守护线程(Daemon Thread)是一种在后台运行的线程,它的存在不会阻止程序的终止。与之相对,用户线程(User Thread)是主要用于执行应用程序逻辑的线程,“如果所有的用户线程都执行完毕,JVM 就会退出,而不管守护线程是否还在运行。”

​ 在 Java 中,我们可以通过 setDaemon(true) 方法将一个线程设置为守护线程。**默认情况下,线程都是用户线程,不会影响程序的终止。**如果想要使用守护线程,可以在创建线程后调用 setDaemon(true) 来设置它为守护线程。


守护线程的主要作用有以下几点:

  1. 后台任务执行: 守护线程通常用于执行一些不需要持续运行的后台任务,如垃圾回收(Garbage Collection)、内存管理等。通过将这些任务放在守护线程中,可以在主要任务执行完毕后,自动进行清理等工作,提高系统的整体性能和资源利用率。
  2. 资源管理: 守护线程可以用于监控和管理一些资源,如数据库连接池、网络连接等当用户线程都结束时,守护线程可以负责关闭这些资源,防止资源泄露和浪费。
  3. 周期性任务: 守护线程还可以用于执行周期性的任务,如定时器任务。这些任务可以在“后台周期性地执行,而不影响主要业务逻辑的进行。”

PS(这很重要基本上就是守护线程的重点):守护线程在程序终止时会突然中断,因此不应该在它们的代码中进行需要确保完整性的操作。另外,守护线程不应该依赖于特定的执行顺序,因为它们的执行可能会受到系统资源调度的影响。


三种创建线程的方式对比

  1. 继承 Thread 类:

    • 创建线程的方式之一是继承 Thread 类,并重写 run 方法来定义线程的任务逻辑。
    • 优点:简单,不需要额外的类来实现接口。
    • 缺点:由于 Java 不支持多继承,因此如果已经继承了其他类,则无法再使用这种方式创建线程。
    • 示例代码:
    class MyThread extends Thread {
        public void run() {
            // 线程执行的任务逻辑
        }
    }
    
    public class ThreadExample {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start();
        }
    }
    
  2. 实现 Runnable 接口:

    • 另一种创建线程的方式是实现 Runnable 接口,并将实现类的实例传递给 Thread 类的构造函数。
    • 优点:可以避免继承单一父类的限制,同时更灵活,可以实现多个接口。
    • 缺点:相对于继承 Thread 类,需要额外的类来实现接口。
    • 示例代码:
    class MyRunnable implements Runnable {
        public void run() {
            // 线程执行的任务逻辑
        }
    }
    
    public class RunnableExample {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
        }
    }
    
  3. 使用 CallableFuture

    • 使用 Callable 接口可以创建一个带有返回结果的任务。Future 接口可以用于获取任务的执行结果。
    • 优点:可以获取任务的返回结果,能够捕获任务抛出的异常。
    • 示例代码:
    import java.util.concurrent.*;
    
    class MyCallable implements Callable<Integer> {
        public Integer call() throws Exception {
            // 线程执行的任务逻辑
            return 42;
        }
    }
    
    public class CallableExample {
        public static void main(String[] args) {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<Integer> future = executor.submit(new MyCallable());
    
            try {
                int result = future.get();
                System.out.println("任务执行结果:" + result);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                executor.shutdown();
            }
        }
    }
    

三种创建线程方式对比小结:

  1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
  3. 继承 Thread 类和实现 Runnable 接口是最基本的方式,而使用 CallableFuture 可以获得更多的控制和返回结果的能力。

线程的几个主要概念

在多线程编程时,你需要了解以下几个概念:

  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复

注意事项和进阶功能

  • 多线程编程需要注意线程安全性和资源竞争问题,合理使用同步机制来保证数据的一致性。

  • Java提供了线程池、并发集合等工具类来简化多线程编程,提高效率和性能。

  • 在处理复杂场景时,可以使用Executor框架、Future接口等实现更高级的线程管理和任务调度。


总结

​ Java多线程编程是实现并发处理的有效手段,可以提高程序性能和响应能力。通过学习线程创建方式、线程同步、线程通信、线程的生命周期、线程的优先级和线程的常用方法,我们可以在应用程序中合理使用多线程来实现并发任务。

作者:Stevedash

发表于:2023年8月13日 20点45分

来源:Java 多线程编程 | 菜鸟教程 (runoob.com)

注:本文内容基于个人学习理解,如有错误或疏漏,欢迎指正。感谢阅读!如果觉得有帮助,请点赞和分享。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Stevedash

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值