Java线程同步机制

news2024/9/29 11:40:52

第1章:引言

大家好,我是小黑。今天咱们来聊聊并发编程,咱们经常听说并行、并发这些词,特别是在处理大量数据、高用户负载时,这些概念就显得尤为重要了。为什么呢?因为并发编程可以帮助咱们的应用程序更有效地使用计算资源,处理更多任务,提升性能。

为什么要同步线程呢?想象一下,如果有多个线程同时对同一个数据进行读写,不加控制地乱来,结果可想而知,数据可能会乱七八糟。所以,咱们需要一种机制来确保数据的一致性和完整性。但这并不简单,同步机制的设计和使用充满了挑战,比如死锁、资源争夺、线程管理等等。

在接下来的章节里,咱们将逐一探讨Java中的各种同步机制,从基础的synchronized关键字到高级的并发工具类,比如CountDownLatchCyclicBarrierSemaphore

第2章:线程基础与同步

在Java中,线程可以看作是程序内部的独立执行路径。当你的程序启动时,Java虚拟机(JVM)会创建一个主线程,但你也可以创建自己的线程来执行特定的任务。这就像是在餐厅里,有多个服务员同时服务不同的桌子,每个服务员就像一个线程,同时处理各自的任务。

但问题来了,如果两个服务员同时去给同一桌客人上菜,可能就会乱套。同理,在Java中,如果多个线程同时操作同一个数据,就可能会引发问题。比如,一个线程在写数据,另一个同时来读,读到的可能就是不完整或者错误的数据。这就是为什么需要线程同步。

来看一个简单的例子。想象有一个共享的计数器,多个线程都要对它进行操作:

public class SharedCounter {
    private int count = 0;

    public void increment() {
        count++; // 增加计数器的值
    }

    public int getCount() {
        return count; // 获取当前计数器的值
    }
}

在这个例子中,如果多个线程同时调用increment()方法,count的值可能不会正确地增加,因为count++这个操作不是原子的,它包含读取count值、增加1、然后写回新值三个步骤。这就需要咱们用同步机制来确保每次只有一个线程能够执行这个操作。

第3章:深入理解synchronized

synchronized的工作原理

让我们先理解一下synchronized是怎么工作的。当一个线程访问某个对象的synchronized方法或代码块时,它会自动获取这个对象的锁。这个锁,就像是一个信号,告诉其他线程:“嘿,我正在使用这个对象,请你们等我用完。”只有当持有锁的线程执行完synchronized代码块,释放了锁,其他线程才能访问这个对象。

使用synchronized同步方法

来看一个例子。假设咱们有一个共享资源,比如一个银行账户:

public class BankAccount {
    private double balance; // 账户余额

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    // 同步方法来存钱
    public synchronized void deposit(double amount) {
        double newBalance = balance + amount;
        balance = newBalance;
    }

    // 同步方法来取钱
    public synchronized void withdraw(double amount) {
        double newBalance = balance - amount;
        balance = newBalance;
    }

    public double getBalance() {
        return balance;
    }
}

在这个例子中,depositwithdraw方法都是synchronized的。这意味着,当一个线程在存或取钱时,其他线程必须等它完成才能进行操作。

使用synchronized同步代码块

除了同步整个方法,咱们也可以只同步方法中的一部分代码。比如,如果只有一小部分代码访问了共享资源,咱们就可以使用`synchronized代码块:

public class Counter {
    private int count = 0;

    public void increment() {
        // 只同步这一小部分代码
        synchronized(this) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

这样,只有对count的增加操作是同步的,其他操作则不受影响。

synchronized的优缺点

synchronized的优点在于它简单易用,而且是Java内建的同步机制,不需要引入额外的库。但它也有缺点。比如,如果一个线程持有锁太久,其他所有需要这个锁的线程都得等着,这可能会导致效率问题。还有,如果不小心使用,可能会引发死锁。

第4章:探索锁(Locks)

锁的基本概念

在Java中,锁是用来控制多个线程对共享资源的访问的一种机制。和synchronized类似,锁也能防止多个线程同时执行特定的代码段。但不同的是,Java的java.util.concurrent.locks包提供的锁更加灵活,比如可以尝试非阻塞地获取锁,或者在尝试获取锁时等待一定的时间。

ReentrantLock的使用示例

让我们通过一个例子来看看如何使用ReentrantLock,这是一个实现了Lock接口的类:

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

public class Counter {
    private final Lock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();  // 获取锁
        try {
            count++;
        } finally {
            lock.unlock();  // 释放锁
        }
    }

    public int getCount() {
        return count;
    }
}

在这个例子中,每当一个线程要执行increment方法时,它首先必须获取锁。如果锁已经被其他线程持有,该线程将等待(或阻塞)直到锁被释放。使用lockunlock方法,我们可以精确控制何时锁定和解锁,这比synchronized提供了更高的灵活性。

锁与synchronized的比较

那么,锁和synchronized有什么不同呢?主要区别在于灵活性和控制力。synchronized是隐式的锁定机制,而锁提供了显式的锁定和解锁操作。这意味着使用锁时,咱们可以更细粒度地控制锁的范围和时机。此外,锁还提供了其他高级功能,比如条件变量(Condition),这些功能在synchronized中是没有的。

第5章:CountDownLatch:协调多个线程

CountDownLatch的原理和使用场景

CountDownLatch是一个同步辅助类,用于延迟线程的进度直到其它线程的操作都完成。想象一下,你是一个赛跑运动员,裁判说:“预备——跑!”但你得等到所有运动员都准备好,枪声响起,比赛才正式开始。CountDownLatch就像是那个发令枪,确保所有线程都准备好了,然后一起出发。

CountDownLatch中,计数器的初始值表示需要等待的线程数量。每个线程完成它的任务后,计数器的值就减一。当计数器值降到零时,表示所有线程都完成了任务,等待在CountDownLatch上的线程就可以继续执行了。

实际代码示例

来看一个具体的例子。假设咱们有一个任务,需要等待三个服务线程都完成工作之后,主线程才能继续:

import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3); // 三个线程

        // 创建并启动三个线程
        for (int i = 1; i <= 3; i++) {
            new Thread(new Worker(i, latch)).start();
        }

        latch.await(); // 等待三个线程完成
        System.out.println("所有服务线程都已完成。主线程继续执行...");
    }

    static class Worker implements Runnable {
        private final int workerNumber;
        private final CountDownLatch latch;

        Worker(int workerNumber, CountDownLatch latch) {
            this.workerNumber = workerNumber;
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                // 模拟工作
                System.out.println("服务线程 " + workerNumber + " 正在执行任务");
                Thread.sleep((long) (Math.random() * 1000 + 500));
                System.out.println("服务线程 " + workerNumber + " 完成任务");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                latch.countDown(); // 完成任务,计数器减一
            }
        }
    }
}

在这个例子中,CountDownLatch确保主线程在所有三个工作线程完成它们的任务之前一直等待。这种方式在并发编程中非常常见,特别是在处理初始化操作或完成一组并发任务之后需要执行汇总操作的场景中。

通过CountDownLatch,咱们可以有效地协调多个线程,确保在继续执行主流程之前,所有的子任务都已经完成。这就是CountDownLatch为咱们提供的强大功能。

第6章:CyclicBarrier:循环屏障的应用

CyclicBarrier的工作机制

CyclicBarrier字面上的意思是“循环屏障”。它允许一组线程相互等待,达到一个公共屏障点(Barrier Point),然后再继续执行。不同于CountDownLatchCyclicBarrier可以重复使用,这就是“循环”(Cyclic)这个词的来源。

举个例子,想象一下接力赛跑,每个跑步者(线程)跑到接力点后要等待其他队员到齐,然后一起跑下一棒。CyclicBarrier就像是那个接力点,确保所有线程都到达后,再一起继续执行。

如何在项目中使用CyclicBarrier

来看一个具体的例子。假设咱们有一个任务,需要四个线程完成各自的部分工作,然后等大家都准备好了,一起执行下一步:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Main {
    public static void main(String[] args) {
        final int THREAD_COUNT = 4;
        CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () ->
                System.out.println("所有线程都到达屏障点,继续执行下一步操作"));

        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(new Task(i, barrier)).start();
        }
    }

    static class Task implements Runnable {
        private final int taskNumber;
        private final CyclicBarrier barrier;

        Task(int taskNumber, CyclicBarrier barrier) {
            this.taskNumber = taskNumber;
            this.barrier = barrier;
        }

        @Override
        public void run() {
            try {
                // 模拟任务
                System.out.println("线程 " + taskNumber + " 正在执行任务");
                Thread.sleep((long) (Math.random() * 1000 + 500));
                System.out.println("线程 " + taskNumber + " 完成任务,等待其他线程");

                barrier.await(); // 在屏障处等待

                // 屏障打开后的操作
                System.out.println("线程 " + taskNumber + " 继续执行后续操作");
            } catch (InterruptedException | BrokenBarrierException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

在这个例子中,每个线程完成它的部分任务后,会在barrier.await();这一行等待。直到所有线程都调用了await()方法,屏障才会打开,然后每个线程继续执行。

CountDownLatch的比较

虽然CyclicBarrierCountDownLatch都能用于线程间的协调,但CyclicBarrier可以被重置并重复使用,而CountDownLatch不能。CyclicBarrier更适用于那些多个线程必须互相等待才能继续执行的场景,而CountDownLatch则适用于一个线程必须等待其他线程完成某些操作的场景。

第7章:Semaphore:信号量的运用

信号量机制介绍

Semaphore是基于计数的同步工具,它可以维护一组许可证(Permits)。如果你想要访问一个资源,就必须先从信号量获取一个许可证。如果信号量中没有许可证可用了,那么请求许可证的线程就必须等待,直到有其他线程释放许可证。

想象一下,你在一个拥挤的餐厅等待座位。餐厅只有一定数量的座位(许可证),如果座位满了,就得等别人用完餐离开,你才能坐下。这就是信号量的工作方式。

Semaphore的实际应用示例

让我们通过一个示例来看看Semaphore是如何在实际中应用的。假设有一个打印机资源池,同时只能有限数量的用户使用打印机:

import java.util.concurrent.Semaphore;

public class PrinterPool {
    private final Semaphore semaphore;

    public PrinterPool(int printerCount) {
        this.semaphore = new Semaphore(printerCount);
    }

    public void usePrinter() {
        try {
            semaphore.acquire(); // 获取许可证
            System.out.println("使用打印机进行打印工作");
            Thread.sleep(1000); // 模拟打印过程
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release(); // 释放许可证
        }
    }
}

在这个例子中,我们创建了一个Semaphore,其许可证数量对应打印机的数量。每当一个线程需要使用打印机时,它会尝试从信号量中获取一个许可证。如果所有的打印机都被占用了,线程将等待,直到有一个打印机变得可用。

适用场景和限制

Semaphore非常适用于那些需要限制对某些资源访问数量的场景。它可以保证只有固定数量的线程同时访问一个资源,这对于资源管理和控制是非常有帮助的。

然而,使用Semaphore时也需要小心。如果不正确地管理许可证的释放,可能会导致资源永远被占用,进而影响系统的稳定性。所以,在使用Semaphore时,要确保在所有情况下都能正确地释放许可证。

第8章:总结与最佳实践

各同步工具的优缺点
  • synchronized:简单易用,但在某些情况下可能导致效率问题,如过长的等待时间或死锁。
  • 锁(Locks):比synchronized提供更多的灵活性和控制力,但使用起来更复杂,且易于误用。
  • CountDownLatch:适用于等待一组操作完成的场景,但是一次性的,用完就没了。
  • CyclicBarrier:适用于在所有线程必须互相等待才能继续的场景,可以重用。
  • Semaphore:强大的资源控制工具,适合限制对资源的并发访问,但需要谨慎管理。
并发编程的最佳实践
  • 避免死锁:确保所有线程以相同的顺序获取锁,避免嵌套锁。
  • 降低锁的粒度:尽可能锁定代码的最小区域,减少等待时间。
  • 使用Java并发包的高级工具:利用java.util.concurrent包提供的工具,如ExecutorServiceFuture等,来简化线程管理和提高效率。
  • 注意资源共享:共享资源应该是线程安全的,或者在访问时进行适当的同步。
  • 避免过度同步:过多的同步可能导致性能问题,找到合适的平衡点。

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

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

相关文章

GO语言笔记1-安装与hello world

SDK开发工具包下载 Go语言官网地址&#xff1a;golang.org&#xff0c;无法访问Golang中文社区&#xff1a;首页 - Go语言中文网 - Golang中文社区下载地址&#xff1a;Go下载 - Go语言中文网 - Golang中文社区 尽量去下载稳定版本&#xff0c;根据使用系统下载压缩包格式的安装…

Dubbo入门介绍和实战

1. 引言 Dubbo是一款开源的高性能、轻量级的Java RPC&#xff08;远程过程调用&#xff09;框架&#xff0c;旨在解决分布式服务之间的通信问题。本文将介绍Dubbo的基础概念、核心特性以及使用场景&#xff0c;包括实际示例演示。 2. 什么是Dubbo&#xff1f; Dubbo是阿里巴…

java碳排放数据信息管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web碳排放数据信息管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环 境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为…

Visual Studio Code安装C#开发工具包并编写ASP.NET Core Web应用

前言 前段时间微软发布了适用于VS Code的C#开发工具包&#xff08;注意目前该包还属于预发布状态但是可以正常使用&#xff09;&#xff0c;因为之前看过网上的一些使用VS Code搭建.NET Core环境的教程看着还挺复杂的就一直没有尝试使用VS Code来编写.NET Core。不过听说C# 开发…

【Java技术专题】「攻破技术盲区」攻破Java技术盲点之unsafe类的使用指南(打破Java的安全管控— sun.misc.unsafe)

Java后门机制 — sun.misc.unsafe 打破Java的安全管控关于Unsafe的编程建议实例化Unsafe后门对象使用sun.misc.Unsafe创建实例单例模式处理实现浅克隆&#xff08;直接获取内存的方式&#xff09;直接使用copyMemory原理分析 密码安全使用Unsafe类—示例代码 运行时动态创建类超…

centos通过yum 安装nginx和基本操作

Yum安装Nginx 1、配置Centos 7 Nginx Yum源仓库(注意系统版本要匹配&#xff0c;此步根据环境来确认&#xff0c;不是必须的&#xff09; rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 2、安装Nginx yum install n…

深度学习|10.5 卷积步长 10.6 三维卷积

文章目录 10.5 卷积步长10. 6 三维卷积![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b5bfa24f57964b0f81f9602f5780c278.png) 10.5 卷积步长 卷积步长是指每计算一次卷积&#xff0c;卷积移动的距离。 设步长为k&#xff0c;原矩阵规模为nxn&#xff0c;核矩阵…

静态网页设计——环保网(HTML+CSS+JavaScript)(dw、sublime Text、webstorm、HBuilder X)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1BC4y1v7ZY/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…

【大数据进阶第三阶段之Hive学习笔记】Hive基础入门

目录 1、什么是Hive 2、Hive的优缺点 2.1、 优点 2.2、 缺点 2.2.1、Hive的HQL表达能力有限 2.2.2、Hive的效率比较低 3、Hive架构原理 3.1、用户接口&#xff1a;Client 3.2、元数据&#xff1a;Metastore 3.3、Hadoop 3.4、驱动器&#xff1a;Driver Hive运行机制…

【设计模式】中介模式

一起学习设计模式 目录 前言 一、概述 二、结构 三、案例实现 四、优缺点 五、使用场景 总结 前言 【设计模式】中介者模式——行为型模式。 一、概述 一般来说&#xff0c;同事类之间的关系是比较复杂的&#xff0c;多个同事类之间互相关联时&#xff0c;他们之间的关…

多功能号卡推广分销管理系统 流量卡推广分销网站源码-目前市面上最优雅的号卡系统

一套完善,多功能,的号卡分销系统,多接口,包括运营商接口,无限三级代理,最简单易用的PHP~ 目前市面上最优雅的号卡系统!没有之一 软件架构说明 环境要求php7.3以上(建议低于8.0),MySQL5.6以上,Nginx1.16(无要求) 产品特性 自动安装向导 易于安装使用部署 多个第…

机器学习笔记 - 基于OpenCV+稀疏光流的无监督运动检测

一、简述 在各种高级开源库的帮助下&#xff0c;检测固定摄像机拍摄的运动行为是轻而易举可以实现的&#xff0c;但检测移动的摄像机拍摄的移动物体的运动检测依然是一个复杂的问题。在这里&#xff0c;我们将继续基于稀疏光流&#xff0c;并检测移动的无人机相机的运动。 这里…

腾讯云轻量应用服务器可以一次性买三年,2核2G4M和2核4G5M

腾讯云优惠之轻量应用服务器3年优惠价格表&#xff0c;目前可以买三年的轻量配置为2核2G4M和2核4G5M&#xff0c;2核2G4M价格三年价格540元&#xff0c;2核4G5M带宽三年756元&#xff0c;当然也可以选择购买一年&#xff0c;第二年续费会比较贵&#xff0c;腾讯云轻量2核2G4M服…

Linux Capabilities 基础概念与基本使用

目录 1. Linux capabilities 是什么&#xff1f; 2. capabilities 的赋予和继承 线程的 capabilities Permitted* 允许 Effective* 有效 Inheritable* 遗传 Bounding&#xff08;集合&#xff09; Ambient 文件的 capabilities Permitted Inheritable Effective 3…

Vue3 结合typescript 组合式函数(1)

在App.vue文件中 实现鼠标点击文件&#xff0c;显示坐标值 第一种方法 第二种方法&#xff1a;组合式函数 结果&#xff1a; 官网推荐组合函数&#xff1a;https://vueuse.org

【MongoDB】MongoDB查询语句find的使用,和提高查询速度的游标的使用,非常详细!!!

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;MongoDB数据库 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续前…

虾皮一键铺货软件:如何使用一键铺货软件在Shopee平台上上架商品

在如今竞争激烈的电商市场中&#xff0c;卖家们都希望能够快速上架商品&#xff0c;以节省时间和精力&#xff0c;并提高销售效率。而在Shopee&#xff08;虾皮&#xff09;平台上&#xff0c;一键铺货软件成为了许多卖家的首选工具。这些软件不仅可以帮助卖家快速将商品批量上…

java基于ssm的房源管理系统+vue论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

Linux:nginx设置网站https

http和https的区别 http: 80 https: 443 这种协议比http协议要安全&#xff0c;因为传输数据是经过加密的 HTTPS简介 HTTPS其实是有两部分组成&#xff1a;HTTP SSL / TLS&#xff0c;也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过…

【附源码】Java计算机毕业设计-图书管理系统

【附源码】Java计算机毕业设计-图书管理系统 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX…