讲师中心 微信公众号
AI工具推荐 视频效率加速

Java多线程安全计数器与周期性打印教程

夜晨姑娘_7984

夜晨姑娘_7984

发布时间:2025-11-23 18:29:19

|

811人浏览过

|

来源于php中文网

原创

java多线程安全计数器与周期性打印教程

本教程详细探讨了在Java多线程环境中安全实现计数器并由另一个线程周期性打印其值的多种方法。文章首先指出直接共享变量的潜在问题,进而介绍了使用`AtomicInteger`进行原子操作的解决方案,以确保数据可见性和线程安全。随后,教程进一步展示了如何利用`LinkedBlockingQueue`实现生产者-消费者模式,通过消息传递机制解耦线程,从而实现更灵活、健壮的并发通信。

在Java多线程编程中,一个常见而又关键的挑战是如何在不同线程之间安全地共享和更新数据。当一个线程负责递增一个计数器,而另一个线程需要周期性地读取并打印这个计数器的当前值时,如果不采取适当的同步机制,很容易遇到数据不一致或可见性问题。本文将深入探讨两种主要的解决方案:利用共享字段配合并发原语,以及通过消息队列实现线程间的解耦通信。

1. 共享字段与并发原语

直接在多个线程之间共享一个普通的int类型变量看似简单,但实际上存在严重的线程安全问题。Java内存模型(JMM)允许线程对共享变量进行本地缓存,并且可以对操作进行重排序,这意味着一个线程对变量的修改可能不会立即对另一个线程可见。

1.1 错误示范与问题分析

考虑以下尝试共享int变量的示例:

立即学习Java免费学习笔记(深入)”;

class CounterExample {
    int x = 5; // 共享变量

    void example() {
        new Thread(this::incrementer).start();
        new Thread(this::printer).start();
    }

    void incrementer() {
        try {
            Thread.sleep(1000L); // 模拟耗时操作
            x += 5;
            System.out.println("Incremented x!");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    void printer() {
        try {
            Thread.sleep(1500L); // 模拟耗时操作
            System.out.println("x is: " + x);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        new CounterExample().example();
    }
}

上述代码在某些情况下可能会打印出x is: 5而不是预期的x is: 10。这是因为printer线程可能读取到x的本地缓存值,而这个缓存值在incrementer线程更新x之后并没有被及时刷新或同步。Java内存模型保证的是“happens-before”关系,如果两个操作之间没有建立明确的happens-before关系,那么它们的执行顺序和可见性是无法保证的。

1.2 使用AtomicInteger确保原子性和可见性

为了解决共享变量的可见性和原子性问题,Java提供了java.util.concurrent.atomic包中的原子类。AtomicInteger是一个常用的原子类,它提供了原子性地更新int值的方法,并隐式地建立了happens-before关系,确保了对变量的修改对其他线程立即可见。

以下是使用AtomicInteger改进后的代码:

import java.util.concurrent.atomic.AtomicInteger;

class SafeCounterExample {
    AtomicInteger x = new AtomicInteger(5); // 使用AtomicInteger

    void example() {
        new Thread(this::incrementer).start();
        new Thread(this::printer).start();
    }

    void incrementer() {
        try {
            Thread.sleep(1000L);
            x.addAndGet(5); // 原子性地增加5并返回新值
            System.out.println("Incremented x!");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    void printer() {
        try {
            Thread.sleep(1500L);
            System.out.println("x is: " + x.get()); // 获取当前值
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        new SafeCounterExample().example();
    }
}

在这个版本中,x.addAndGet(5)操作是原子性的,并且x.get()会读取到最新的值。AtomicInteger内部通过CAS(Compare-And-Swap)操作保证了操作的原子性,并通过内存屏障确保了可见性。

Alibabacloud Sdk Client Initialization For Java
Alibabacloud Sdk Client Initialization For Java

在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。

下载

注意事项:

  • volatile关键字也能保证变量的可见性,但它不保证复合操作(如x++)的原子性。对于简单的读写操作,volatile可能适用,但对于需要原子性更新的计数器,AtomicInteger是更优选择。
  • 对于更复杂的临界区操作,可以使用synchronized关键字或java.util.concurrent.locks包中的Lock接口(如ReentrantLock)来保护共享资源。

2. 消息队列实现生产者-消费者模式

除了直接共享字段,另一种强大且更解耦的线程通信方式是使用消息队列(或称作通道)。在这种模式下,一个线程(生产者)将数据放入队列,另一个线程(消费者)从队列中取出数据。java.util.concurrent包提供了多种并发集合,如LinkedBlockingQueue,非常适合实现这种模式。

2.1 LinkedBlockingQueue实现示例

LinkedBlockingQueue是一个可选容量的阻塞队列,它在队列满时生产者会阻塞,在队列空时消费者会阻塞,从而天然地处理了线程间的同步问题。

以下是使用LinkedBlockingQueue实现生产者-消费者模式的计数器示例:

import java.util.concurrent.LinkedBlockingQueue;

class MessageBusCounterExample {
    // 容量为100的阻塞队列,用于传递计数器的值
    private final LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(100);

    void example() {
        new Thread(this::producer).start();
        new Thread(this::consumer).start();
    }

    // 生产者线程:负责递增计数器并将值放入队列
    void producer() {
        try {
            for (int i = 0; i < 1000; i++) {
                queue.put(i); // 将当前计数器值放入队列,如果队列满则阻塞
                System.out.println("Added to queue: " + i);
                Thread.sleep(50L); // 模拟生产间隔
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    // 消费者线程:负责从队列中取出值并打印
    void consumer() {
        try {
            while (true) {
                int value = queue.take(); // 从队列中取出值,如果队列空则阻塞
                System.out.println("Retrieved from queue: " + value);

                // 模拟周期性打印的逻辑,例如每10个值打印一次并等待
                if (value % 10 == 0) {
                    System.out.println("Value " + value + " is divisible by 10, waiting a while...");
                    Thread.sleep(200L);
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        new MessageBusCounterExample().example();
    }
}

在这个示例中,producer线程负责生成递增的整数并将其放入队列。consumer线程则从队列中取出这些整数并进行处理。queue.put()和queue.take()方法自动处理了线程间的同步和等待,使得代码更加简洁和安全。

注意事项:

  • 消息队列模式天然地解耦了生产者和消费者,它们不需要直接知道对方的存在,只需知道队列即可。
  • 这种模式在处理复杂业务逻辑时非常有用,可以方便地扩展生产者和消费者数量。
  • 对于更复杂的跨进程或跨服务通信,可以考虑使用专业的分布式消息队列系统(如Kafka、RabbitMQ)或数据库事务。

总结

在Java多线程环境中实现计数器并周期性打印其值,核心在于正确处理线程间的共享数据和同步问题。

  1. 对于简单的计数器操作,推荐使用AtomicInteger等原子类。它们提供了原子性的操作,并保证了数据在不同线程间的可见性,避免了复杂的锁机制。
  2. 对于需要更灵活的线程间通信和解耦的场景,生产者-消费者模式结合LinkedBlockingQueue等阻塞队列是优秀的解决方案。它通过消息传递机制,简化了并发编程的复杂性,并提供了天然的流量控制。

理解Java内存模型和happens-before关系是编写正确并发程序的关键。选择合适的并发工具和模式,能够确保多线程应用的健壮性、性能和可维护性。

相关文章

全能打印神器
全能打印神器

全能打印神器是一款非常好用的打印软件,可以在电脑、手机、平板电脑等设备上使用。支持无线打印和云打印,操作非常简单,使用起来也非常方便,有需要的小伙伴快来保存下载体验吧!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

LibLibAI
LibLibAI Hot

一款AI视频创作工具,主要用于国内领先的AI创意平台,以海量模型、低门槛操作与“创作-分享-商业化”生态,让小白与专业创作者都能高效实现图文乃至视频创意表达,适合需要提升相关任务效率的用户。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

相关专题

更多
rabbitmq和kafka有什么区别
rabbitmq和kafka有什么区别

rabbitmq和kafka的区别:1、语言与平台;2、消息传递模型;3、可靠性;4、性能与吞吐量;5、集群与负载均衡;6、消费模型;7、用途与场景;8、社区与生态系统;9、监控与管理;10、其他特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

504

2024.02.23

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

668

2026.01.28

RabbitMQ使用教程合集
RabbitMQ使用教程合集

RabbitMQ使用教程合集整理 RabbitMQ 基础教程、消息队列开发案例、生产者消费者、交换机与队列实战内容。

164

2026.05.20

RabbitMQ集群部署指南
RabbitMQ集群部署指南

RabbitMQ集群部署指南聚合 RabbitMQ 集群部署、高可用架构、镜像队列、故障恢复、监控与运维优化内容。

97

2026.05.20

RabbitMQ Docker实战指南
RabbitMQ Docker实战指南

RabbitMQ Docker实战指南提供 RabbitMQ Docker 镜像部署、Docker Compose、Kubernetes Operator 与云原生实践教程。

122

2026.05.20

什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

1873

2023.08.11

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

2454

2023.10.07

kafka消费者组有什么作用
kafka消费者组有什么作用

kafka消费者组的作用:1、负载均衡;2、容错性;3、广播模式;4、灵活性;5、自动故障转移和领导者选举;6、动态扩展性;7、顺序保证;8、数据压缩;9、事务性支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2146

2024.01.12

Vibeknow在线使用入口合集
Vibeknow在线使用入口合集

本专题汇总了Vibeknow在线创作视频的官方入口及网页版使用教程,涵盖PPT、PDF、Word等文档一键转讲解视频的核心操作,并整理了免费版水印规则与手机端浏览器访问指南,助你快速将知识内容视频化。

0

2026.09.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn