解密Java多线程同步:掌握线程间同步与互斥技巧

news2025/1/12 12:05:34

哈喽,各位小伙伴们,你们好呀,我是喵手。

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

  在现代软件开发中,多线程是一项重要的技术,而线程间的同步与互斥是其中关键的一部分。本文将揭秘Java多线程同步的奥秘,帮助读者掌握线程间同步与互斥技巧。

摘要

  本文将全面解析Java多线程同步技术,包括线程间通信、锁、条件变量等。我们将深入讨论如何实现线程的同步与互斥,以及应对线程安全问题的技巧。

简介

  多线程编程中,线程间的同步与互斥是保证数据一致性和程序正确性的关键。在本节中,我们将简要介绍多线程编程的挑战和线程间同步的重要性。

源代码解析

  通过源代码解析,我们将深入研究Java多线程同步的实现方式。我们将介绍关键的同步和互斥机制,如synchronized关键字、Lock、Condition等,并解释它们的用法和注意事项。

应用场景案例

  本节将提供一些实际应用场景的案例,展示Java多线程同步技术在解决并发问题时的应用。包括生产者消费者模型、读写锁、线程池等常见场景的解决方案。

应用场景案例

生产者消费者模型

  生产者消费者模型是一个常见的并发场景,其中生产者线程负责生产数据并将其存入共享的缓冲区,而消费者线程从缓冲区中获取数据并进行处理。在这种情况下,使用合适的同步机制来保证生产者和消费者线程之间的同步和互斥是非常重要的。

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        Thread producerThread = new ProducerThread(buffer);
        Thread consumerThread = new ConsumerThread(buffer);
        producerThread.start();
        consumerThread.start();
    }

    static class Buffer {
        private List<Integer> data = new ArrayList<>();
        private int maxSize = 5;

        public synchronized void produce(int value) throws InterruptedException {
            while (data.size() >= maxSize) {
                wait();
            }
            data.add(value);
            System.out.println("Produced: " + value);
            notify();
        }

        public synchronized int consume() throws InterruptedException {
            while (data.size() == 0) {
                wait();
            }
            int value = data.get(0);
            data.remove(0);
            System.out.println("Consumed: " + value);
            notify();
            return value;
        }
    }

    static class ProducerThread extends Thread {
        private Buffer buffer;

        public ProducerThread(Buffer buffer) {
            this.buffer = buffer;
        }

        @Override
        public void run() {
            try {
                for (int i = 1; i <= 10; i++) {
                    buffer.produce(i);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class ConsumerThread extends Thread {
        private Buffer buffer;

        public ConsumerThread(Buffer buffer) {
            this.buffer = buffer;
        }

        @Override
        public void run() {
            try {
                for (int i = 1; i <= 10; i++) {
                    int value = buffer.consume();
                    Thread.sleep(2000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

代码解析:

  这里给大家详细解读下如上代码,该代码是一个生产者消费者模型的例子。生产者线程和消费者线程共享一个缓冲区,生产者线程向缓冲区中生产数据,消费者线程从缓冲区中消费数据。

  1. 在主函数中,创建了一个缓冲区对象和生产者线程对象、消费者线程对象,并启动了这两个线程。

  2. 缓冲区的produce方法用于生产数据。当缓冲区已满时,调用wait方法使该线程进入等待状态。当缓冲区有空闲位置时,生产数据并打印出来,然后调用notify方法唤醒可能在等待的其他线程。

  3. 缓冲区的consume方法用于消费数据。当缓冲区为空时,调用wait方法使该线程进入等待状态。当缓冲区有数据时,取出第一个数据并从缓冲区中移除,然后打印出来,最后调用notify方法唤醒可能在等待的其他线程。

  4. 生产者线程类和消费者线程类继承自Thread类,并在run方法中调用缓冲区的produceconsume方法,分别生产和消费数据。生产者线程每隔一秒生产一个数据,消费者线程每隔两秒消费一个数据。

  5. 整个程序的运行过程是,生产者线程先生产数据加入缓冲区,然后消费者线程从缓冲区中取出数据消费,然后再生产,再消费,循环往复。

读写锁

在某些场景下,读写操作的并发访问可能会导致数据不一致性和并发性能问题。为此,Java提供了ReentrantReadWriteLock类,它允许多个线程同时读取共享资源,但只允许一个线程进行写操作。

public class ReadWriteLockExample {
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static String data = "";

    public static void main(String[] args) {
        Thread writer1 = new WriterThread("Thread 1");
        Thread reader1 = new ReaderThread("Thread 2");
        Thread reader2 = new ReaderThread("Thread 3");

        writer1.start();
        reader1.start();
        reader2.start();
    }

    static class WriterThread extends Thread {
        private String name;

        public WriterThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            lock.writeLock().lock();
            try {
                data += " Written by " + name;
                System.out.println(data);
            } finally {
                lock.writeLock().unlock();
            }
        }
    }

    static class ReaderThread extends Thread {
        private String name;

        public ReaderThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            lock.readLock().lock();
            try {
                System.out.println("Data read by " + name + ": " + data);
            } finally {
                lock.readLock().unlock();
            }
        }
    }
}

代码解析:

  这里给大家详细解读下如上代码,该代码演示了如何使用Java中的ReentrantReadWriteLock实现读写锁。

  1. 我们创建了一个ReentrantReadWriteLock对象,命名为lock,用于管理读写锁。然后,我们创建了一个静态字符串变量data,用于存储数据。在main方法中,我们创建了一个写线程writer1和两个读线程reader1和reader2。然后,我们启动这些线程。

  2. 写线程WriterThread是一个内部类,继承自Thread类。它接收一个名字作为参数,并重写了run方法。在run方法中,我们首先获取写锁(lock.writeLock().lock()),然后将数据写入data变量,并打印出来。最后,释放写锁(lock.writeLock().unlock())。

  3. 读线程ReaderThread是另一个内部类,也继承自Thread类。它接收一个名字作为参数,并重写了run方法。在run方法中,我们首先获取读锁(lock.readLock().lock()),然后打印出data变量的内容。最后,释放读锁(lock.readLock().unlock())。

  这里我们通过使用读写锁,我们可以实现多个线程同时读取数据,但只允许一个线程写入数据。这可以提高程序的并发性能。

  这里需要注意的是,在实际应用中,我们可以根据具体需求进行更多的错误处理和线程同步操作。例如,在数据读取过程中,如果写线程正在写入数据,我们可以通过使用Condition对象来挂起读线程。

线程池

  当我们在需要处理大量任务的情况下,线程池是一种有效的方式来管理线程资源。Java提供了线程池框架,可以方便地创建和管理线程池,并可避免线程频繁创建和销毁的开销。

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            Task task = new Task(i);
            executor.execute(task);
        }
        executor.shutdown();
    }

    static class Task implements Runnable {
        private int id;

        public Task(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            System.out.println("Task " + id + " is running on " + Thread.currentThread().getName());
        }
    }
}

代码解析:

  这里给大家详细解读下如上代码,它具体演示了如何使用Java的Executor框架中的线程池来执行任务。

  1. 在主方法中创建了一个固定大小为3的线程池,即最多同时执行3个任务。

  2. 使用一个循环创建了10个任务,并将这些任务提交给线程池进行执行。每个任务都是一个Task对象,Task实现了Runnable接口,表示它是一个可以通过Thread运行的任务。当任务被执行时,它会打印出当前任务的ID和执行任务的线程的名称。

  3. 通过调用线程池的shutdown方法,告诉线程池不再接受新的任务,并且等待已经提交的任务执行完成。

  当所有的任务执行完成后,线程池会自动关闭。总的来说,这段代码展示了线程池的使用方式,通过将任务提交给线程池,可以实现并发执行任务的效果,并且可以控制并发的程度。

小结

  我们在面对Java多线程同步,我们可以知道,它是实现高效并发编程的重要技术之一。通过掌握线程间同步与互斥的技巧,我们能够开发出性能优越、可靠稳定的多线程应用程序。在实际开发中,根据具体的场景和需求,选择合适的同步机制非常重要。希望本文能够帮助读者掌握Java多线程同步技术,高效应用于实际项目中,构建出高质量的并发应用程序。

优缺点分析

  在本节中,我们将分析Java多线程同步技术的优点和缺点。我们将讨论性能开销、死锁和竞态条件等方面的考虑,以全面评估多线程同步的适用性。

类代码方法介绍

  在本节中,我们将详细介绍Java中与线程同步相关的类和方法。我们将重点介绍synchronized关键字、Lock接口和Condition接口的用法和原理。

具体的Java代码测试用例

  为了验证Java多线程同步的正确性和可靠性,我们将编写具体的Java代码测试用例。我们将模拟多线程并发访问共享资源的情况,观察同步机制的表现和效果。

package com.example.javase.ms.threadDemo;

/**
 * @Author ms
 * @Date 2023-12-16 18:05
 */
public class ThreadSyncExample {
    public static void main(String[] args) {
        // 创建共享资源对象
        SharedResource resource = new SharedResource();

        // 创建多个线程并启动
        for (int i = 0; i < 5; i++) {
            // 传入共享资源对象
            Thread thread = new WorkerThread(resource);
            thread.start();
        }
    }

    static class SharedResource {
        // 共享资源数据和方法
    }

    static class WorkerThread extends Thread {
        private SharedResource resource;

        public WorkerThread(SharedResource resource) {
            this.resource = resource;
        }

        @Override
        public void run() {
            // 访问和操作共享资源
        }
    }
}

测试结果展示:

根据如上测试用例,我本地执行结果如下:

image.png

代码解析:

  如下针对上述测试代码,给大家具体讲解下,仅供参考:

  如上代码我主要是演示了如何创建一个共享资源对象,并使用多个线程对这个共享资源进行访问和操作。

  1. 在main方法中,创建了一个SharedResource对象,并将其作为参数传递给WorkerThread的构造函数。然后使用一个循环创建了5个WorkerThread对象,并启动这些线程。

  2. 在SharedResource类中,可以定义共享资源的数据和方法,具体内容根据实际需求来确定。

  3. WorkerThread类继承自Thread类,并具有一个SharedResource对象作为成员变量。在run方法中,可以编写具体的代码来访问和操作共享资源。

  其中,这里需要注意的是,由于多个线程会同时访问共享资源,可能会导致竞态条件和数据不一致的问题。为了解决这个问题,可以使用同步(synchronization)机制来确保只有一个线程可以访问共享资源,从而避免并发访问的问题。具体的同步方式可以使用synchronized关键字或者Lock接口来实现。

  以上就是这段代码的简单解析,希望能够帮助同学们理解多线程编程中的共享资源访问和同步问题。

全文小结

  在本节中,我们将对全文的要点进行小结,强调Java多线程同步的关键知识和技巧。我们将回顾线程同步的重要性和应用场景,并强调实践的重要性。

总结

  通过本文的学习,我们深入了解了Java多线程同步的关键技术和技巧。掌握线程间同步与互斥的技术,对于保证程序的正确性和性能至关重要。我们鼓励读者深入学习和实践Java多线程同步技术,使用合适的同步机制解决并发编程中的问题。

  希望本文能够帮助读者全面理解和应用Java多线程同步技术,提升并发编程能力,构建高效可靠的多线程应用程序。通过灵活运用同步与互斥技巧,我们能够充分发挥多核处理器的性能,提高程序的并发性和响应能力。

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript:js实现在线五子棋人机(人人)对弈

在线五子棋人机对弈 全部使用前端技术,使用HTML,CSS以及JS进行实现. 棋盘在后端就是一个15*15的二维数组 页面设计 页面设计的比较粗糙 主要使用js自带的canvas画布进行绘画 HTML代码如下: <div class"outer"><canvas id"canvas" height&qu…

linux权限维持(四)

6.inetd服务后门 inetd 是一个监听外部网络请求 ( 就是一个 socket) 的系统守护进程&#xff0c;默认情况下为 13 端口。当 inetd 接收到 一个外部请求后&#xff0c;它会根据这个请求到自己的配置文件中去找到实际处理它的程序&#xff0c;然后再把接收到的 这个socket 交给那…

B2B企业如何做好谷歌Google广告推广营销布局?

当今全球化的商业环境中&#xff0c;B2B企业要想在激烈的市场竞争中脱颖而出&#xff0c;拓展海外市场成为了必经之路。而谷歌Google广告&#xff0c;作为全球最大的在线广告平台&#xff0c;无疑是企业触达全球潜在客户的黄金钥匙。云衔科技通过专业服务助力企业轻松开户与高效…

CST初级教程 二

本教程将讲解CST Studio的视窗操控的基本操作. 3D视窗的快捷操作 动态放大与缩小&#xff08;Dynamic Zoom&#xff09; 将鼠标指针移动到CST Studio图形视窗中&#xff0c;向上滚动鼠标滚轮&#xff0c;可动太放大图形视窗中的显示内容&#xff0c;向下滚动鼠标滚轮即可动态缩…

非对称渐开线齿轮学习笔记分享

最近有小伙伴遇到了非对称渐开线齿轮的加工问题,花了些时间学习了解一下,下面是总结的学习笔记,有兴趣的朋友可以瞅瞅: 目录: 为什么要采用非对称? 非对称有什么优点? 非对称齿形如何加工? 非对称齿轮怎么测量? 非对称齿轮建模 为什么要采用非对称? 现在的传动要求…

Linux:进程创建 进程终止

Linux&#xff1a;进程创建 & 进程终止 进程创建fork写时拷贝 进程终止退出码strerrorerrno 异常信号exit 进程创建 fork fork函数可以用于在程序内部创建子进程&#xff0c;其包含在头文件<unistd.h>中&#xff0c;直接调用fork()就可以创建子进程了。 示例代码&…

【C语言】深入理解KMP算法及C语言实现

一、KMP算法简介 KMP算法&#xff08;Knuth-Morris-Pratt算法&#xff09;是一种高效的字符串匹配算法&#xff0c;由Donald Knuth、James H. Morris和 Vaughan Pratt共同发明。KMP算法的核心思想是当一次字符比较失败时&#xff0c;利用已经得到的部分匹配信息&#xff0c;将模…

JVM虚拟机监控及性能调优实战

目录 jvisualvm介绍 1. jvisualvm是JDK自带的可以远程监控内存&#xff0c;跟踪垃圾回收&#xff0c;执行时内存&#xff0c;CPU/线程分析&#xff0c;生成堆快照等的工具。 2. jvisualvm是从JDK1.6开始被继承到JDK中的。jvisualvm使用 jvisualvm监控远程服务器 开启远程监控…

【Java框架】SpringMVC(三)——异常处理,拦截器,文件上传,SSM整合

目录 异常处理解释局部异常处理全局异常 拦截器拦截器介绍作用:拦截器和过滤器之间的区别拦截器执行流程代码实现补充 文件上传依赖配置MultipartResolver编写文件上传表单页APIMultipartFileFile.separator必须对上传文件进行重命名代码示例 SpringMVC文件上传流程多文件上传 …

mybatis中<if>条件判断带数字的字符串失效问题

文章目录 一、项目背景二、真实错误原因说明三、解决方案3.1针对纯数字的字符串值场景3.2针对单个字符的字符串值场景 四、参考文献 一、项目背景 MySQL数据库使用Mybatis查询拼接select语句中进行<if>条件拼接的时候&#xff0c;发现带数字的或者带单个字母的字符串失效…

Coursera: An Introduction to American Law 学习笔记 Week 03: Property Law

An Introduction to American Law 本文是 https://www.coursera.org/programs/career-training-for-nevadans-k7yhc/learn/american-law 这门课的学习笔记。 文章目录 An Introduction to American LawInstructors Week 03: Property LawKey Property Law TermsSupplemental Re…

LM324的输出VOL与IOL你注意过吗?

电路图 途中LMC6084 更改为LM324 故障现象 这个电路的输入输出表达式为 R30 两端电压等于0V 当J16 的4脚与2脚相等&#xff0c;等于5V&#xff08;或者4脚略大于2脚时&#xff09;7脚输出 约 500mV&#xff1b; 实际应该为0V左右才对.见下图 故障原因 上图运放输出低电平…

AI重塑数字安全,安恒信息行胜于言

有人曾言&#xff1a;所有行业都值得基于人工智能技术重做一遍。 深以为然。如今&#xff0c;数字安全产业面临着一次重要的重塑机遇。以大模型为代表的人工智能技术正深刻影响着数字安全市场格局、产品研发、技术方案以及运营服务。产业界已形成共识&#xff0c;即谁能抓住人…

Nginx+Lua+OpenResty(详解及使用)

一、 Nginx简介 Nginx是一个高性能的Web服务器和反向代理的软件。 Web服务器&#xff1a;就是运行我们web服务的容器&#xff0c;提供web功能&#xff0c;还有tomcat也提供类似的功能。 代理是软件架构和网络设计中&#xff0c;非常重要的一个概念。 二、Nginx的反向代理&…

WEB服务的配置与使用 Apache HTTPD

服务端&#xff1a;服务器将发送由状态代码和可选的响应正文组成的 响应 。状态代码指示请求是否成功&#xff0c;如果不成功&#xff0c;则指示存在哪种错误情况。这告诉客户端应该如何处理响应。较为流星的web服务器程序有&#xff1a; Apache HTTP Server 、 Nginx 客户端&a…

百度网盘svip白嫖永久手机2024最新教程

百度网盘&#xff08;原名百度云&#xff09;是百度推出的一项云存储服务&#xff0c;已覆盖主流PC和手机操作系统&#xff0c;包含Web版、Windows版、Mac版、Android版、iPhone版和Windows Phone版。用户将可以轻松将自己的文件上传到网盘上&#xff0c;并可跨终端随时随地查看…

爬虫抓取网站数据

Fiddler 配置fiddler工具结合浏览器插件 配置fiddler Tools--Options 抓包技巧 谷歌浏览器开启无痕浏览,使用SwitchyOmega配置好代理端口 Ctrl x 清理所有请求记录,可以删除指定不需要日志方便观察 设置按请求顺序 观察cookie,观察请求hesder cookie和row返回结果 Swit…

《QT实用小工具·四十二》圆形发光图像

1、概述 源码放在文章末尾 该项目实现了图像的发光效果&#xff0c;特别适合做头像&#xff0c;项目demo演示如下所示&#xff1a; 项目部分代码如下所示&#xff1a; import QtQuick 2.7 import QtGraphicalEffects 1.12Item {id: rootwidth: 80height: 80property int ra…

写Python需要养成的9个编程好习惯

以写Python代码为例&#xff0c;有以下9个编程好习惯。 1. 提前设计 写代码和写作文一样&#xff0c;需要有大纲&#xff0c;不然很容易变成"屎山"。 思考业务逻辑和代码流程&#xff0c;是动手前的准备工作&#xff0c;这上面可以花一半以上时间。 一些程序员洋…

【考研数学】全程跟张宇,《1000题》应该怎么搭配《660》《880》?

数一125学长来了&#xff0c;选题集和选老师经验满满&#xff0c;手把手教你避雷&#xff0c;直接不废话&#xff0c;三本题集很重要&#xff0c;筛选找重点事半功倍。 就老师而言&#xff0c;如果已经决定全程跟张宇老师了&#xff0c;那么就不要中途换老师&#xff01; 就题…