百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

一文搞懂Java多线程核心知识点

liebian365 2025-03-01 14:33 18 浏览 0 评论

在 Java 开发领域,多线程编程是一项极为重要的技能。它不仅能够显著提升程序的性能和响应速度,还能让我们充分利用现代多核处理器的强大计算能力。对于 Java 开发程序员而言,深入理解和熟练掌握 Java 多线程相关知识,是从初级迈向中高级开发的关键一步。接下来,让我们一起深入探索 Java 多线程的世界。

线程基础概念

线程,简单来说,是程序执行流的最小单元。在 Java 中,它是轻量级的进程,每个线程都有自己独立的栈空间,用于存储局部变量、方法调用等信息,但它们共享进程的堆内存,堆内存中存放着对象实例等数据。打个比方,线程就像是一个公司里的员工,每个员工都有自己独立的办公桌(栈空间),方便自己处理工作事务,但大家共享公司的办公场地(堆内存),在这个公共空间里协作完成项目任务。这种共享与独立的特性,使得多线程编程既强大又复杂。

线程创建方式

继承 Thread 类

通过继承 Thread 类,并重写其 run 方法,就可以创建一个自定义线程类。在 run 方法中编写线程执行的具体逻辑,然后创建该子类的实例,并调用 start 方法来启动线程。例如:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("This is a thread created by extending Thread class.");
    }
}
// 使用时
MyThread myThread = new MyThread();
myThread.start();

这种方式的优点是代码简单直观,直接继承 Thread 类,方便对线程进行个性化的控制。但缺点也很明显,由于 Java 是单继承语言,一旦继承了 Thread 类,就无法再继承其他类,这在一定程度上限制了类的扩展性。

实现 Runnable 接口

实现 Runnable 接口也是创建线程的常见方式。首先创建一个实现 Runnable 接口的类,并实现其 run 方法,然后将该实现类的实例传递给 Thread 类的构造函数来创建线程。示例代码如下:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("This is a thread created by implementing Runnable interface.");
    }
}
// 使用时
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();

与继承 Thread 类相比,实现 Runnable 接口的方式更加灵活,因为一个类可以实现多个接口,这样就避免了单继承的限制,使代码的可维护性和扩展性更强。同时,这种方式也更符合面向接口编程的思想,将线程的执行逻辑与线程本身进行了解耦。

实现 Callable 接口

Java 5.0 引入了 Callable 接口,它与 Runnable 接口类似,也是用于创建线程执行的任务。但 Callable 接口的 run 方法可以有返回值,并且可以抛出异常。使用 Callable 接口时,需要借助 FutureTask 类来获取任务的执行结果。示例代码如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable {
    @Override
    public Integer call() throws Exception {
        // 模拟一些计算任务
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

public class CallableExample {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();

        try {
            Integer result = futureTask.get();
            System.out.println("The result is: " + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

这种方式在需要获取线程执行结果的场景下非常有用,比如在一些需要异步计算并获取结果的任务中,Callable 接口提供了更强大的功能支持。

线程状态

Java 线程有六种状态:新建(New)、运行(Runnable)、阻塞(Blocked)、等待(Waiting)、计时等待(Timed_Waiting)和终止(Terminated)。如下所示。

新建状态(New)

当使用 new 关键字创建一个线程对象时,线程处于新建状态。此时线程还没有开始执行,只是在内存中分配了相应的资源,包括线程对象和其所属的栈空间等。例如:

Thread thread = new Thread();

这个 thread 对象就处于新建状态,它还没有被启动,不会执行任何实际的任务。

运行状态(Runnable)

当调用线程的 start 方法后,线程进入运行状态。此时线程已经被调度器安排执行,它会在 CPU 上运行其 run 方法中的代码。需要注意的是,Runnable 状态实际上包含了两个子状态:就绪状态和运行中状态。就绪状态表示线程已经准备好执行,但还没有被 CPU 调度执行;而运行中状态则表示线程正在 CPU 上执行。在多线程环境下,由于 CPU 资源有限,多个线程会在就绪状态和运行中状态之间不断切换。

阻塞状态(Blocked)

当线程试图获取一个被其他线程占用的锁,或者在等待 I/O 操作完成时,线程会进入阻塞状态。在阻塞状态下,线程不会占用 CPU 资源,直到引起阻塞的原因消除。例如,当一个线程调用 synchronized 方法或进入 synchronized 代码块时,如果该锁已经被其他线程持有,那么当前线程就会进入阻塞状态,等待锁的释放。

等待状态(Waiting)

线程调用 wait () 方法、join () 方法,或者 LockSupport.park () 方法时,会进入等待状态。在等待状态下,线程会释放所持有的锁(如果有的话),并且不会被 CPU 调度执行,直到其他线程调用 notify ()、notifyAll () 方法或者 unpark () 方法来唤醒它。等待状态常用于线程之间的协作,比如一个线程等待另一个线程完成某个任务后再继续执行。

计时等待状态(Timed_Waiting)

与等待状态类似,计时等待状态也是线程暂停执行的一种状态,但它有一个时间限制。线程调用 sleep (long millis) 方法、wait (long timeout) 方法、join (long millis) 方法或者 LockSupport.parkNanos (long nanos)、LockSupport.parkUntil (long deadline) 方法时,会进入计时等待状态。在指定的时间内,如果没有其他线程唤醒它,线程会自动恢复到运行状态。这种状态在需要控制线程执行时间间隔或者设置等待超时的场景下非常有用。

终止状态(Terminated)

当线程的 run 方法执行完毕,或者因为异常而提前终止时,线程进入终止状态。此时线程已经完成了它的任务,不再需要 CPU 资源,其占用的系统资源(如栈空间等)也会被回收。

线程同步

在多线程环境下,由于多个线程共享同一堆内存,当它们同时访问和修改共享数据时,就可能会出现线程安全问题,导致数据不一致或程序运行结果错误。为了解决这些问题,Java 提供了多种线程同步机制。

synchronized 关键字

synchronized 关键字是 Java 中最基本的线程同步工具,它可以修饰方法或代码块。当一个线程进入被 synchronized 修饰的方法或代码块时,它会自动获取对象的锁(如果是静态方法,则获取类的锁),在方法或代码块执行完毕后,会自动释放锁。这样就保证了同一时刻只有一个线程能够访问被 synchronized 修饰的部分,从而避免了数据竞争和线程安全问题。例如:

class SynchronizedExample {
    public synchronized void synchronizedMethod() {
        // 线程安全的代码
    }
}

上述代码中,synchronizedMethod 方法被 synchronized 关键字修饰,当一个线程调用该方法时,会获取 SynchronizedExample 对象的锁,其他线程如果也想调用该方法,就必须等待锁的释放。

Lock 接口

Java 5.0 引入了
java.util.concurrent.locks.Lock 接口,它提供了比 synchronized 关键字更灵活和强大的线程同步控制。Lock 接口的实现类(如 ReentrantLock)提供了更细粒度的锁控制,例如可以实现公平锁和非公平锁,还可以使用 tryLock () 方法尝试获取锁,如果获取不到锁可以立即返回,而不是像 synchronized 关键字那样一直等待。示例代码如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class LockExample {
    private Lock lock = new ReentrantLock();

    public void lockMethod() {
        lock.lock();
        try {
            // 线程安全的代码
        } finally {
            lock.unlock();
        }
    }
}

在使用 Lock 接口时,需要注意手动释放锁,通常将解锁操作放在 finally 块中,以确保无论代码是否发生异常,锁都能被正确释放。

volatile 关键字

volatile 关键字主要用于解决变量在多线程环境下的可见性问题。当一个变量被声明为 volatile 时,它会保证所有线程对该变量的访问都是直接从主内存中读取,而不是从线程的工作内存中读取,从而避免了缓存不一致的问题。例如:

class VolatileExample {
    private volatile boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    public boolean isFlag() {
        return flag;
    }
}

在上述代码中,flag 变量被声明为 volatile,当一个线程修改了 flag 的值后,其他线程能够立即看到这个变化,保证了数据的可见性。需要注意的是,volatile 关键字并不能保证原子性,即多个线程对 volatile 变量的复合操作(如自增、自减等)仍然可能会出现线程安全问题。

线程池

在实际应用中,频繁地创建和销毁线程会带来较大的开销,影响程序的性能。为了解决这个问题,Java 提供了线程池的概念。线程池是一种基于池化技术的多线程处理方式,它预先创建一定数量的线程,并将这些线程放在一个线程池中,当有任务需要执行时,从线程池中获取一个空闲线程来执行任务,任务执行完毕后,线程不会被销毁,而是返回线程池等待下一个任务。这样可以大大减少线程创建和销毁的开销,提高程序的性能和响应速度。

Java 提供了多种线程池的实现类,如 ThreadPoolExecutor、FixedThreadPool、CachedThreadPool、SingleThreadExecutor 等。其中,ThreadPoolExecutor 是最基础的线程池实现类,其他几个线程池都是通过对 ThreadPoolExecutor 进行不同的参数配置来实现特定的功能。例如,创建一个固定大小的线程池可以使用 Executors 类的静态方法:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小为5的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 10; i++) {
            final int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskNumber + " is being executed by " + Thread.currentThread().getName());
                // 模拟任务执行
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

在上述代码中,通过
Executors.newFixedThreadPool (5) 创建了一个固定大小为 5 的线程池,然后向线程池中提交了 10 个任务。由于线程池大小为 5,所以这 10 个任务会分批次执行,每个任务由线程池中的一个线程来执行。当所有任务执行完毕后,调用 executorService.shutdown () 方法关闭线程池。

总结

Java 多线程编程是一个复杂而又强大的领域,掌握好它对于 Java 开发程序员来说至关重要。通过深入理解线程的基础概念、创建方式、状态转换、同步机制、线程池以及多线程的应用场景,我们能够编写出高效、稳定、并发性能强的 Java 程序。在实际开发中,我们需要根据具体的业务需求和场景,合理地运用多线程技术,充分发挥其优势,同时避免出现线程安全问题和性能瓶颈。希望本文能帮助你对 Java 多线程有更深入的理解和掌握,让你在 Java 开发的道路上更上一层楼。

相关推荐

“版本末期”了?下周平衡补丁!国服最强5套牌!上分首选

明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...

VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...

东营交警实名曝光一批酒驾人员名单 88人受处罚

齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...

Qt界面——搭配QCustomPlot(qt platform)

这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...

大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写

老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...

测试谷歌VS Code AI 编程插件 Gemini Code Assist

用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...

顾爷想知道第4.5期 国服便利性到底需优化啥?

前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...

掌握Visual Studio项目配置【基础篇】

1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...

还嫌LED驱动设计套路深?那就来看看这篇文章吧

随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...

Visual Studio Community 2022(VS2022)安装图文方法

直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...

Qt添加MSVC构建套件的方法(qt添加c++11)

前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...

Qt为什么站稳c++GUI的top1(qt c)

为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...

qt开发IDE应该选择VS还是qt creator

如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...

Qt 5.14.2超详细安装教程,不会来打我

Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...

Cygwin配置与使用(四)——VI字体和颜色的配置

简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...

取消回复欢迎 发表评论: