Java多线程知识点深度总结

一、引言

    在Java编程中,多线程是一个重要且复杂的主题。多线程允许程序同时执行多个任务,从而提高了程序的执行效率和响应速度。然而,多线程编程也带来了同步、通信和数据一致性等问题。因此,深入理解Java多线程的知识点对于编写高效、稳定的多线程程序至关重要。本文将详细探讨Java多线程的基本概念、线程创建与启动、线程状态与生命周期、线程同步与通信以及线程池等关键知识点。

二、Java多线程基本概念

    进程与线程

进程是系统分配资源的基本单位,它包含了一个程序的执行实例及其拥有的系统资源(如内存、文件、设备等)。线程是CPU调度的基本单位,它是进程中的一个执行单元,负责执行进程中的一段程序。一个进程可以包含多个线程,这些线程共享进程的资源,但拥有独立的执行路径和状态。

    并发与并行

并发是指多个任务在同一时间段内交替执行,而并行是指多个任务在同一时刻同时执行。在单核CPU上,多线程程序实际上是并发执行的,因为CPU在同一时刻只能处理一个线程;而在多核CPU上,多线程程序可以并行执行,因为不同的线程可以在不同的核心上同时运行。

三、线程的创建与启动

在Java中,创建和启动线程主要有两种方式:继承Thread类和实现Runnable接口。

    1.继承Thread类

通过继承Thread类并重写其run()方法,可以创建自定义的线程类。然后,创建该线程类的实例并调用其start()方法,即可启动线程。start()方法会调用线程的run()方法,从而使线程开始执行。

示例代码:

public class MyThread extends Thread {  
    @Override  
    public void run() {  
        // 线程执行的代码  
    }  
  
    public static void main(String[] args) {  
        MyThread thread = new MyThread();  
        thread.start(); // 启动线程  
    }  
}

    2.实现Runnable接口

通过实现Runnable接口并重写其run()方法,可以创建可运行的线程任务。然后,将该任务作为参数传递给Thread类的构造函数,创建Thread对象并调用其start()方法,即可启动线程。这种方式更加灵活,因为Java不支持多重继承,但可以实现多个接口。此外,将线程任务与代码分离也便于管理和复用。

示例代码:

public class MyRunnable implements Runnable {  
    @Override  
    public void run() {  
        // 线程执行的代码  
    }  
  
    public static void main(String[] args) {  
        MyRunnable task = new MyRunnable();  
        Thread thread = new Thread(task);  
        thread.start(); // 启动线程  
    }  
}

四、线程状态与生命周期

    Java中的线程具有五种状态:新建(NEW)、就绪(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超时等待(TIMED_WAITING)和终止(TERMINATED)。这些状态反映了线程在其生命周期中的不同阶段。

  1. 新建状态(NEW):当线程对象被创建但尚未启动时,它处于新建状态。
  2. 就绪状态(RUNNABLE):当线程启动后,它进入就绪状态,等待CPU的调度执行。
  3. 阻塞状态(BLOCKED):当线程试图获取一个内部的对象锁(而不是java.util.concurrent库中的锁),而该锁被其他线程持有时,该线程进入阻塞状态。当持有锁的线程释放锁后,该线程将变成就绪状态。
  4. 等待状态(WAITING):线程通过调用另一个线程的join方法、或者调用LockSupport.park方法、或者等待一个条件变量(Condition)时,该线程进入等待状态。直到其他线程做了某些特定动作(如唤醒该线程),该线程才返回到就绪状态。
  5. 超时等待状态(TIMED_WAITING):这是线程等待另一个线程执行一个(唤醒)动作的最长时间,或者等待某件事情的发生的最长时间。在指定的等待时间一到或者接收到其他线程的执行动作,该线程就返回就绪状态。
  6. 终止状态(TERMINATED):表示线程已经执行完毕。

线程的生命周期从创建开始,经历就绪、阻塞、等待等状态,最终结束于终止状态。了解这些状态和状态转换有助于我们更好地控制和管理线程。

五、线程同步与通信

  由于多个线程共享进程的资源,因此线程之间需要进行同步和通信以确保数据的一致性和避免竞态条件。Java提供了多种机制来实现线程的同步与通信。

    1.synchronized关键字

  synchronized关键字是Java中最基本的线程同步机制。它可以用来修饰方法或代码块,以确保同一时刻只有一个线程能够执行被修饰的方法或代码块。synchronized关键字通过获取对象的内部锁来实现同步。

示例代码(synchronized关键字):

public class SynchronizedExample {  
    private Object lock = new Object();  
  
    public void synchronizedMethod() {  
        synchronized (this) {  
            // 同步代码块,同一时刻只能有一个线程执行这里的代码  
        }  
    }  
  
    public static void main(String[] args) {  
        SynchronizedExample example = new SynchronizedExample();  
        Thread thread1 = new Thread(() -> example.synchronizedMethod());  
        Thread thread2 = new Thread(() -> example.synchronizedMethod());  
        thread1.start();  
        thread2.start();  
    }  
}

在上述示例中,synchronizedMethod方法使用了synchronized关键字修饰,确保同一时刻只有一个线程能够执行该方法。此外,synchronized也可以修饰代码块,通过指定一个锁对象来实现更细粒度的同步。

    2.volatile关键字

volatile关键字用于声明变量,它保证了对该变量的修改能够立即被其他线程看到。volatile变量只能确保可见性,不能保证原子性。因此,对于复合操作,仍需要使用其他同步机制。

示例代码(volatile关键字):

public class VolatileExample {  
    private volatile boolean flag = false;  
  
    public void setFlag(boolean flag) {  
        this.flag = flag;  
    }  
  
    public boolean getFlag() {  
        return flag;  
    }  
  
    public static void main(String[] args) {  
        VolatileExample example = new VolatileExample();  
        Thread thread1 = new Thread(() -> {  
            example.setFlag(true);  
            // 其他操作...  
        });  
        Thread thread2 = new Thread(() -> {  
            while (!example.getFlag()) {  
                // 等待flag变为true  
            }  
            // 执行相应操作...  
        });  
        thread1.start();  
        thread2.start();  
    }  
}

在上面的示例中,flag变量被声明为volatile,确保当一个线程修改flag的值时,其他线程能够立即看到修改后的值。

  1. wait()和notify()方法

wait()notify()方法是Java对象内置的方法,用于实现线程之间的通信。wait()方法使当前线程等待,直到其他线程调用notify()notifyAll()方法唤醒它。这些方法通常与synchronized关键字一起使用,因为它们需要在同步代码块或同步方法中调用。

示例代码(wait()和notify()):

public class WaitNotifyExample {  
    private int count = 0;  
    private final Object lock = new Object();  
  
    public void increment() {  
        synchronized (lock) {  
            while (count == 5) {  
                try {  
                    lock.wait(); // 当前线程等待  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
            count++;  
            System.out.println("Incremented count to: " + count);  
            lock.notifyAll(); // 唤醒所有等待的线程  
        }  
    }  
  
    public void decrement() {  
        synchronized (lock) {  
            while (count == 0) {  
                try {  
                    lock.wait(); // 当前线程等待  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
            count--;  
            System.out.println("Decremented count to: " + count);  
            lock.notifyAll(); // 唤醒所有等待的线程  
        }  
    }  
  
    public static void main(String[] args) {  
        WaitNotifyExample example = new WaitNotifyExample();  
        Thread incrementThread = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                example.increment();  
            }  
        });  
        Thread decrementThread = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                example.decrement();  
            }  
        });  
        incrementThread.start();  
        decrementThread.start();  
    }  
}

在这个示例中,increment()decrement()方法分别用于增加和减少count的值。当count达到5或降至0时,线程会调用wait()方法进入等待状态,直到其他线程调用notifyAll()方法唤醒它。通过这种方式,线程之间可以进行协调和通信。

六、线程池

线程池是Java中用于优化线程管理的工具,它允许我们预先创建一组线程并保存在内存中,避免了频繁创建和销毁线程所带来的性能开销。线程池可以复用线程,管理线程的生命周期,并且提供了灵活的调度策略。

Java标准库中的java.util.concurrent包提供了多种线程池的实现,包括FixedThreadPoolCachedThreadPoolScheduledThreadPool等。

以下是FixedThreadPool的一个简单示例:

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
  
public class ThreadPoolExample {  
    public static void main(String[] args) {  
        // 创建一个固定大小的线程池,线程数量为3  
        ExecutorService executor = Executors.newFixedThreadPool(3);  
  
        // 提交多个任务到线程池  
        for (int i = 0; i < 10; i++) {  
            final int taskId = i;  
            executor.submit(() -> {  
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());  
                // 模拟任务执行时间  
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            });  
        }  
  
        // 关闭线程池,不再接受新任务,等待已提交任务执行完毕  
        executor.shutdown();  
        while (!executor.isTerminated()) {  
            // 等待所有任务执行完毕  
        }  
        System.out.println("All tasks are done.");  
    }  
}

 在上面的代码中,我们首先使用Executors.newFixedThreadPool方法创建了一个固定大小为3的线程池。然后,我们提交了10个任务到线程池。每个任务都会输出当前任务的ID和正在执行任务的线程名称,并模拟了一个耗时操作。

提交完所有任务后,我们调用executor.shutdown()方法关闭线程池,这表示线程池不再接受新的任务,但是会继续执行已经提交的任务。我们使用executor.isTerminated()方法检查线程池中的所有任务是否都执行完毕。

线程池的好处包括:

  1. 降低资源消耗:通过复用线程,避免了线程的频繁创建和销毁,从而减少了系统开销。
  2. 提高响应速度:当任务到达时,如果线程池中有空闲线程,可以立即执行,无需等待线程的创建。
  3. 提高系统的稳定性:通过线程池管理线程的生命周期,可以有效避免大量线程同时执行导致系统资源耗尽的情况。

当谈到线程池时,还有一些重要的概念和细节需要了解。

线程池的主要类型

  1. FixedThreadPool:固定大小的线程池。当线程池中的线程数达到核心线程数时,新的任务会在队列中等待,直到有线程空闲出来。如果队列满了,而线程池中的线程数还没有达到最大线程数,则会创建新的线程来执行任务。如果线程数已经达到最大线程数,并且队列也满了,就会根据配置的拒绝策略来处理新提交的任务。

  2. CachedThreadPool:可缓存的线程池。当线程池中的线程数超过处理任务所需的线程数时,空闲的线程会在指定的时间内自动销毁,以减少系统开销。如果当前线程池中的线程数小于核心线程数,即使线程池中的其他线程是空闲的,也会创建一个新线程来处理新提交的任务。这种线程池比较适合执行大量的短时任务。

  3. ScheduledThreadPool:支持定时及周期性任务执行的线程池。它除了能像FixedThreadPool和CachedThreadPool那样接收任务之外,还可以接收ScheduledFuture<?>类型的任务,表示延迟执行或定期执行的任务。

线程池的拒绝策略

当线程池中的线程都在忙碌,并且工作队列已满时,如果继续提交任务,就需要有一种策略来处理这种情况,这就是拒绝策略。Java提供了四种内置的拒绝策略:

  1. AbortPolicy:直接抛出RejectedExecutionException异常阻止系统正常运行。
  2. CallerRunsPolicy:调用execute方法的线程来执行该任务。
  3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
  4. DiscardPolicy:不处理,直接丢弃掉。

当然,也可以根据需要实现RejectedExecutionHandler接口,自定义拒绝策略。

线程池的关闭

当不再需要线程池时,应该调用shutdown()shutdownNow()方法来关闭它。shutdown()方法会启动线程池的关闭序列,线程池不再接受新的任务,但是会等待所有已提交的任务执行完毕。而shutdownNow()方法会尝试停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

线程池的监控

线程池还提供了一些方法来监控其状态,如getPoolSize()(返回线程池中的线程数)、getActiveCount()(返回正在执行任务的线程数)、getQueue()(返回任务队列)等。这些方法可以帮助我们更好地了解线程池的运行情况,从而进行调优。

线程池的调优

线程池的调优主要涉及到核心线程数、最大线程数、队列容量等参数的设定。这些参数的设定需要根据具体的业务场景和系统资源来决定。一般来说,核心线程数可以设置为系统可以承受的同时处理的任务数,最大线程数可以设置为系统可以承受的最大并发任务数,队列容量则可以根据任务的生成速度和处理速度来设定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/580339.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI大模型探索之路-训练篇3:大语言模型全景解读

文章目录 前言一、语言模型发展历程1. 第一阶段&#xff1a;统计语言模型&#xff08;Statistical Language Model, SLM&#xff09;2. 第二阶段&#xff1a;神经语言模型&#xff08;Neural Language Model, NLM&#xff09;3. 第三阶段&#xff1a;预训练语言模型&#xff08…

顺通拖鞋ERP企业销售管理系统:驱动销售业绩飙升的利器

顺通企业销售管理系统通过集成客户信息、销售流程、数据分析等功能&#xff0c;帮助企业全面提升销售效率和业绩&#xff0c;成为驱动销售业绩飙升的利器。此外&#xff0c;系统还支持销售流程的可视化展示&#xff0c;使销售人员能够清晰地了解销售进展&#xff0c;及时调整销…

短视频账号矩阵系统===4年技术源头打磨

短视频矩阵系统技术源头打磨需要从多个方面入手&#xff0c;以下是一些建议&#xff1a; 1. 基础技术研发&#xff1a;不断投入资金和人力进行基础技术研发&#xff0c;包括但不限于视频处理、人工智能、大数据等技术&#xff0c;以提高短视频矩阵系统的性能和稳定性。 2. 优化…

JAVA面试八股文之JVM

JVM JVM由那些部分组成&#xff0c;运行流程是什么&#xff1f;你能详细说一下 JVM 运行时数据区吗&#xff1f;详细介绍一下程序计数器的作用&#xff1f;你能给我详细的介绍Java堆吗?什么是虚拟机栈&#xff1f;栈内存溢出情况&#xff1f;堆栈的区别是什么吗&#xff1f;解…

深入理解分布式事务② ---->分布式事务基础(MySQL 的 4 种事务隔离级别【读未提交、读已提交、可重复读、串行化】的最佳实践演示)详解

目录 深入理解分布式事务② ----&#xff1e;分布式事务基础&#xff08;MySQL 的 4 种事务隔离级别【读未提交、读已提交、可重复读、串行化】的最佳实践演示&#xff09;详解1、MySQL 事务基础1-1&#xff1a;MySQL 中 4 种事务隔离级别的区别1-2&#xff1a;MySQL 中 4 种事…

Qt使用OPCUA

假如想在Qt下使用OPCUA通讯&#xff0c;貌似大家都是倾向于使用【qtopcua】这个库。但是在Qt6之前&#xff0c;假如想使用这个库&#xff0c;还得自己编译&#xff0c;比较繁琐。假如想开箱即用&#xff0c;而且没有使用太复杂的功能的话&#xff0c;其实可以直接使用open62541…

2024年最新一线互联网企业高级软件测试工程师面试题大全

1、功能测试 功能测试是游戏测试中跟“玩游戏”最相关的一个环节。 当然这里的“玩”不是要真的让你感受快乐&#xff0c;而是要通过“玩”游戏&#xff0c;发现存在的问题或不合理的地方。因此&#xff0c;这个“玩”的过程基本不会感受到游戏的乐趣。事实上&#xff0c;每一次…

决策树学习笔记

一、衡量标准——熵 随机变量不确定性的度量 信息增益&#xff1a;表示特征X使得类Y的不确定性减少的程度。 二、数据集 14天的打球情况 特征&#xff1a;4种环境变化&#xff08;天气、温度等等&#xff09; 在上述数据种&#xff0c;14天中打球的天数为9天&#xff1b;不…

LVGL移植

Lvgl介绍 LVGL是一个开源的图形库&#xff0c;专为嵌入式系统设计。它提供了丰富的图形元素和功能&#xff0c;可以帮助开发者快速构建现代化的用户界面。LVGL具有跨平台的特性&#xff0c;支持多种操作系统和硬件平台&#xff0c;包括ARM Cortex-M&#xff0c;ESP32&#xff…

基于springboot+vue+Mysql的漫画网站

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

等保测评与信息安全管理体系认证的区别

区别一、标准以及性质 等保测评以《中华人民共和国计算机信息系统安全保护条例》为基础&#xff0c;结合一系列的政策和标准&#xff0c;对信息安全水平进行评估。而安全管理系统的认证&#xff0c;是资讯安全管理系统的一种规范&#xff0c;本身并不具备强制性质。企业可根据…

这么全的权限系统设计方案,不值得收藏吗?

1 为什么需要权限管理 日常工作中权限的问题时时刻刻伴随着我们&#xff0c;程序员新入职一家公司需要找人开通各种权限&#xff0c;比如网络连接的权限、编码下载提交的权限、监控平台登录的权限、运营平台查数据的权限等等。 在很多时候我们会觉得这么多繁杂的申请给工作带…

未来想从事Linux 后台开发,需要学习linux内核吗?

先列出主要观点&#xff0c;有时间再补充细节&#xff1a; “学习Linux内核”对不同的人有不同的含义&#xff0c;学习方法、侧重点、投入的精力也大不相同。我大致分三类&#xff1a;reader、writer、hacker。reader 就是了解某个功能在内核的大致实现 how does it work&…

ZIP压缩输入流(将文件压缩为ZIP文件)

文章目录 前言一、ZIP压缩输入流是什么&#xff1f;二、使用介绍 1.使用方法2.实操展示总结 前言 该篇文章将会介绍如何使用java代码将各种文件&#xff08;文件夹&#xff09;的资源压缩为一个ZIP压缩包。通过java.util包中的ZipOutputStream类来实现。并且需要自定义压缩方法…

记录些AI Agents设计模式和NL2SQL知识

吴恩达分享的四种 自我反思&#xff08;Reflection&#xff09;&#xff1a;可以自我修正&#xff1b;使用工具&#xff08;Tool Use&#xff09;&#xff1a;链接其他系统去做一些事情&#xff0c;比如把电脑里面的未归档文件做好归档&#xff1b;规划&#xff08;Planning&a…

免费实用在线小工具

免费在线工具 https://orcc.online/ pdf在线免费转word文档 https://orcc.online/pdf 时间戳转换 https://orcc.online/timestamp Base64 编码解码 https://orcc.online/base64 URL 编码解码 https://orcc.online/url Hash(MD5/SHA1/SHA256…) 计算 https://orcc.online/ha…

软考之零碎片段记录(二十七)+复习巩固(十三、十四)

学习 1. 案例题 涉及到更新的。肯能会是数据流的终点E, P, D 数据流转。可能是 P->EP->D(数据更新)P->P(信息处理)D->P(提取数据信息) 2. 案例2 补充关系图时会提示不增加新的实体。则增加关联关系 3. 案例3 用例图 extend用于拓展&#xff0c;当一个用例…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-1.3

前言&#xff1a; 本文是根据哔哩哔哩网站上视频“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”的学习笔记&#xff0c;在这里会记录下正点原子Linux ARM MX6ULL 开发板根据配套的哔哩哔哩学习视频所作的实验和笔记内容。本文大量的引用了正点原子哔哔哩网…

Python 单例类中设置和获取属性的问题及解决方案

1、问题背景 在编写 Python 代码时&#xff0c;有时需要创建一个单例类&#xff0c;这样就可以在程序中使用该类的唯一实例。为了实现这一点&#xff0c;可以定义一个类&#xff0c;并在其 __new__ 方法中检查该类的实例是否已经存在。如果实例存在&#xff0c;则返回该实例&a…

stm32f4单片机强制类型转换为float程序跑飞问题

如题&#xff0c;在一个数据解析函数中使用了*(float *)&data[offset]&#xff0c;其中data为uint8类型指针&#xff0c;指向的value地址为 可以看到地址0x20013A31非对齐&#xff0c;最终在执行VLDR指令时导致跑飞 VLDR需要使用对齐访问 跑飞后查看SCB寄存器发现确实是非…
最新文章