多线程---线程同步,线程通信

news2025/1/26 15:34:13

线程同步

1.概述

线程同步是多线程编程中的一个重要概念,它指的是在多线程环境中,通过一定的机制保证多个线程按照某种特定的方式正确、有序地执行。这主要是为了避免并发问题,如死锁、竞态条件、资源争用等,确保数据的一致性和完整性。

当多个线程共享同一份资源时,由于线程的执行顺序是不确定的,可能会出现线程安全问题。例如,两个线程同时对一个共享变量进行操作,可能会出现预期之外的结果。

如下:

小明和小弘对同一账号取钱,会出现余额为负的情况

package Synchronization;
//操作账户
public class Account {
    private String cardId;
    private Double amount;

    public Account(String cardId, Double amount) {
        this.cardId = cardId;
        this.amount = amount;
    }
    public void withDrawMoney(Double amount){
        if(this.amount>=amount){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.amount=this.amount-amount;
            System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
        }else {
            System.out.println(Thread.currentThread().getName()+"来取钱失败!");
        }

    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public Double getAmount() {
        return amount;
    }

    public void setAmount(Double amount) {
        this.amount = amount;
    }
}

package Synchronization;

public class Main {
    public static void main(String[] args) {
        Account account = new Account("w2xId", (double) 1000);//初始化账户
        //实例化小明取钱线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                account.withDrawMoney((double) 1000);
            }
        },"小明").start();
        //实例化小弘取钱线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                account.withDrawMoney((double) 1000);
            }
        },"小弘").start();
    }
}

结果:

为了避免这种情况,就需要对线程进行同步,即保证同一时刻只有一个线程可以对共享资源进行操作。

2.线程同步的三种方式

1.同步代码块

在Java中,同步代码块是一种确保线程同步的机制,它允许你指定一段代码只能由一个线程在任何给定时间执行。同步代码块是通过在代码块前加上synchronized关键字和一个锁对象来实现的。这个锁对象可以是任何对象,当线程尝试进入同步代码块时,它必须首先获得这个锁。

关键修改部分

//同步代码块
synchronized (this) {
    if(this.amount>=amount){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.amount=this.amount-amount;
        System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
    }else {
        System.out.println(Thread.currentThread().getName()+"来取钱失败!");
    }
}

这里取this作为锁对象,this指代Account对象,也就是Acount对象为锁对象,且此程序只有一个Account实例化对象。

执行结果

2.同步方法

在Java中,同步方法也是一种实现线程同步的常用方式。通过在方法声明前加上synchronized关键字,可以确保该方法在任何时刻只被一个线程访问。同步方法会隐式地锁定当前实例(this),即如果两个线程同时访问同一个对象的同一个同步方法,那么只有一个线程能够执行该方法,另一个线程必须等待。

关键代码修改如下:

synchronized public void withDrawMoney(Double amount){
    //同步代码块
        if(this.amount>=amount){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.amount=this.amount-amount;
            System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
        }else {
            System.out.println(Thread.currentThread().getName()+"来取钱失败!");
        }

}

3.Lock锁

在Java中,除了使用内置的synchronized关键字实现同步外,还可以使用java.util.concurrent.locks包中提供的Lock接口及其实现类(如ReentrantLock)来实现更灵活和强大的线程同步。Lock接口提供了一种机制,可以显式地获取和释放锁,而不是像synchronized那样隐式地获取和释放锁。

使用Lock接口的主要优势包括:

  1. 灵活性:可以中断正在等待锁的线程,可以尝试获取锁而不必阻塞,以及可以释放锁,即使锁是由其他线程获取的。
  2. 可响应性:相比于synchronized,Lock通常具有更好的响应性,因为它允许更细粒度的锁控制。
  3. 条件支持:Lock接口与Condition接口一起使用,可以实现更复杂的线程同步需求。

Lock锁实现步骤:

1.创建Lock锁

2.加锁

3.解锁

关键代码修改如下:

package Synchronization;

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

public class Account {
    private String cardId;
    private Double amount;
 
    private final Lock lock=new ReentrantLock();//1.创建锁对象
    public Account(String cardId, Double amount) {
        this.cardId = cardId;
        this.amount = amount;
    }
//    synchronized public void withDrawMoney(Double amount){
//        //同步代码块
//            if(this.amount>=amount){
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                this.amount=this.amount-amount;
//                System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
//            }else {
//                System.out.println(Thread.currentThread().getName()+"来取钱失败!");
//            }
//
//    }
synchronized public void withDrawMoney(Double amount){
    lock.lock();//2.加锁
    if(this.amount>=amount){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.amount=this.amount-amount;
        System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
    }else {
        System.out.println(Thread.currentThread().getName()+"来取钱失败!");
    }
    lock.unlock();//3.解锁

}

//    public void withDrawMoney(Double amount){
//        //同步代码块
//        synchronized (this) {
//            if(this.amount>=amount){
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                this.amount=this.amount-amount;
//                System.out.println(Thread.currentThread().getName()+"来取钱成功!余额为"+this.amount);
//            }else {
//                System.out.println(Thread.currentThread().getName()+"来取钱失败!");
//            }
//        }
//
//    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public Double getAmount() {
        return amount;
    }

    public void setAmount(Double amount) {
        this.amount = amount;
    }
}

线程通信

1.概述

生产者消费者模型在线程通信中是一个经典的应用场景。这个模型主要用来解决生产者和消费者之间的同步问题,确保两者之间的顺畅协作。在这个模型中,生产者负责生成数据并将其放入共享缓冲区,而消费者则从缓冲区中取出数据进行处理。

生产者消费者模型的关键点:

  1. 共享缓冲区:通常是一个队列或其他数据结构,用作生产者和消费者之间的通信媒介。生产者将生产的数据放入缓冲区,而消费者从缓冲区中取出数据进行处理。
  2. 同步机制:由于生产者和消费者可能同时访问缓冲区,因此需要一种同步机制来确保数据的一致性和避免竞态条件。这通常通过锁、信号量或其他同步原语来实现。在Java中,可以使用synchronized关键字、Lock接口或BlockingQueue来实现同步。
  3. 生产者和消费者的协作:当缓冲区满时,生产者需要等待缓冲区有空闲空间才能继续生产数据。同样,当缓冲区为空时,消费者需要等待缓冲区中有数据才能继续消费。这种协作通过线程间的通信来实现,生产者通知消费者缓冲区有新数据,消费者通知生产者缓冲区有空闲空间。

2.实例

三个生产者生产包子,两个消费者吃包子,每次生产者将一个包子放到桌子上并通知消费者,消费者拿取包子后通知生产者生产包子。

package Synchronization;

import java.util.ArrayList;
import java.util.List;

/**
 * 如果桌子上没有包子,则拿包子线程等待,唤醒其他所有线程
 * 如果桌子上有包子,则放包子线程等待,唤醒其他所有线程
 */
public class Desk {
    private List<String> list=new ArrayList<>();
    //放包子,通过同步代码块,保证生产者只有一个在生产包子
    public synchronized void put(){

        String name = Thread.currentThread().getName();
        if(list.size()==0){
            list.add("生产了一个包子");
            System.out.println(name+list.get(0));
            try {
                Thread.sleep(2000);
                this.notifyAll();//唤醒所有的线程
                this.wait();//等待
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else {
            this.notifyAll();//唤醒所有的线程
            try {
                this.wait();//等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //拿包子,通过同步代码块,保证只有一个消费者拿取包子
    public synchronized void get(){

        String name = Thread.currentThread().getName();
        if(list.size()==1){
            list.clear();
            System.out.println(name+"拿了一个包子");
            try {

                this.notifyAll();//唤醒所有的线程
                this.wait();//等待
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else {
            this.notifyAll();//唤醒所有的线程
            try {
                this.wait();//等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
package Synchronization;

public class CommunicationModel {
    public static void main(String[] args) {
        Desk desk=new Desk();
        //创建三个生产者线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    desk.put();
                }

            }
        },"厨师1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    desk.put();
                }
            }
        },"厨师2").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    desk.put();
                }
            }
        },"厨师3").start();
        //创建两个消费者线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    desk.get();
                }
            }
        },"客人1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    desk.get();
                }
            }
        },"客人2").start();

    }
}

3.结果

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

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

相关文章

超强!中科院1区算法改进!改进雪融优化GVSAO-CNN-BiGRU-Attention融合注意力机制预测程序代码!直接运行!

适用平台&#xff1a;Matlab2023版及以上 雪消融优化算法&#xff08;Snow Ablation Optimizer&#xff0c;SAO&#xff09;&#xff0c;于2023年6月发表在SCI、中科院1区顶级期刊《Expert Systems With Applications》上。该算法刚刚提出&#xff0c;目前还没有套用这个算法的…

【Java EE初阶十七】网络原理(二)

2. 传输层 2.2 TCP协议 2.2.2 关于可靠传输 4.滑动窗口 前面的三个机制&#xff0c;都是在保证 tcp 的可靠性&#xff1b; TCP 的可靠传输,是会影响传输的效率的.(多出了一些等待 ack 的时间,单位时间内能传输的数据就少了)&#xff1b; 滑动窗口,就让可靠传输对性能的影响,更…

【摸鱼日常】使用Docker部署RPG网页小游戏

一、本次实践介绍 1. 本次实践简介 本次实践部署环境为个人测试环境&#xff0c;快速使用docker部署RPG网页小游戏。 rootWellDone:/home/goodjob# uname -a Linux WellDone 6.5.0-14-generic #14~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Nov 20 18:15:30 UTC 2 x86_64 x86_…

SG5032VAN晶体振荡器规格书

SG5032VAN是一款具备高精度、高稳定性及多功能的石英晶体振荡器,通过锁相环技术和AT晶体单元实现宽频率范围,能够满足各种应用场景的需求。频率范围从73.5 MHz到700 MHz,可以满足多种无线通信设备和系统&#xff0c;供电电压2.5V/3.3V,为用户提供了更加灵活的电源选择。还具备输…

通俗的讲解什么是机器学习之损失函数

想象一下&#xff0c;你在玩一个靶心射击的游戏&#xff0c;你的目标是尽可能让箭簇命中靶心。在这个游戏中&#xff0c;损失函数可以看作是测量你的箭簇与靶心距离的规则。损失函数的值越小&#xff0c;意味着你的箭簇离靶心越近&#xff0c;你的射击技能越好。 在机器学习中…

Mouse Anti-HDM IgE Antibody Assay Kit

哮喘作为一种常见的慢性炎症类疾病&#xff0c;影响着全世界约3亿各年龄段的人。哮喘一般是由于暴露于过敏原&#xff08;尘螨、宠物皮屑、花粉及霉菌等&#xff09;引起的&#xff0c;其特征是气流阻塞和支气管痉挛。屋尘螨&#xff08;house dust mite, HDM&#xff09;是最常…

贪心算法练习day2

删除字符 1.题目及要求 2.解题思路 1&#xff09;初始化最小字母为‘Z’&#xff0c;确保任何字母都能与之比较 2&#xff09;遍历单词&#xff0c;找到当前未删除字母中的最小字母 3&#xff09;获取当前位置的字母 current word.charAt(i)&#xff1b; 4&#xff09;删…

BUGKU-WEB 变量1

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; flag In the variable !<?php error_reporting(0); include "flag1.php"; highlight_file(__file__); if(isset($_GET[args])){$args $_GET[args];if(!preg_match("/^\w$/",$args…

深度学习系列53:大模型微调概述

参考系列文章&#xff1a;https://zhuanlan.zhihu.com/p/635152813 github链接&#xff1a;https://github.com/liguodongiot/llm-action 1 训练范式 下面这种instructive learning&#xff0c;在模型参数达到80亿后&#xff0c;会发生质的提升&#xff1a; 类似的还有手写pr…

error: ‘QWidget‘ file not found

说明你没有加载 widgets模块 缺少widgets&#xff0c;就报错

【Java EE初阶十二】网络原理(二)

2. 传输层 2.2 TCP协议 2.2.2 关于可靠传输 4.滑动窗口 前面的三个机制&#xff0c;都是在保证 tcp 的可靠性&#xff1b; TCP 的可靠传输,是会影响传输的效率的.(多出了一些等待 ack 的时间,单位时间内能传输的数据就少了)&#xff1b; 滑动窗口,就让可靠传输对性能的影响,更…

CSS 圆形的时钟秒针状的手柄绕中心点旋转的效果

<template><!-- 创建一个装载自定义加载动画的容器 --><view class="cloader"><!-- 定义加载动画主体部分 --><view class="clface"><!-- 定义类似秒针形状的小圆盘 --><view class="clsface"><!-…

从概念到现实:数字孪生在智慧园区建设中的实现路径

目录 一、引言 二、数字孪生与智慧园区的关联 三、数字孪生技术的核心要素及实现路径 1、数据采集与处理 2、建立数字孪生模型 3、实时更新与优化 4、实现与智慧园区的集成 四、数字孪生技术在智慧园区中的应用场景 1、能源管理 2、安全监控 3、设施管理 五、面临的…

解读OpenAI视频生成模型Sora背后的原理:Diffusion Transformer

Diffusion Models视频生成-博客汇总 前言&#xff1a;OpenAI最近推出的视频生成模型Sora在效果上实现了真正的遥遥领先&#xff0c;很多博主都介绍过Sora&#xff0c;但是深入解读背后原理的博客却非常少。Sora的原理最主要的是核心模型主干《Scalable Diffusion Models with T…

119. Pascal‘s Triangle II(杨辉三角 II)

问题描述 给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 问题分析 其实这个问题与118.杨辉三角解决思路一样&#xff0c;只不过此问题只返回杨辉三角的某一行而已。 代码 i…

BUGKU-WEB eval

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; <?phpinclude "flag.php";$a $_REQUEST[hello];eval( "var_dump($a);");show_source(__FILE__); ?>解题思路 PHP代码审计咯 相关工具 百度搜索PHP相关知识 解题步骤 分析脚…

JDK-JVM-hotspot

JVM JDKJDK内部体系结构&#xff1a;JVM 与 跨平台JVM在程序运行过程中的运行细节&#xff0c;内存分配 和 流转模型。JVM结构体系1. 虚拟机栈2. 线程栈2.1. 栈帧2.2. 数据结构栈 与 线程栈 的关系&#xff1a;2.3.栈帧的内部结构&#xff1a;2.4 方法中的数据 在栈帧中的流转过…

《Go 简易速速上手小册》第7章:包管理与模块(2024 最新版)

文章目录 7.1 使用 Go Modules 管理依赖 - 掌舵向未来7.1.1 基础知识讲解7.1.2 重点案例&#xff1a;Web 服务功能描述实现步骤扩展功能 7.1.3 拓展案例 1&#xff1a;使用数据库功能描述实现步骤扩展功能 7.1.4 拓展案例 2&#xff1a;集成 Redis 缓存功能描述实现步骤扩展功能…

代码随想录刷题笔记 DAY 28 | 复原 IP 地址 No.93 | 子集 No.78 | 子集 II No.90

文章目录 Day 2801. 复原 IP 地址&#xff08;No. 93&#xff09;1.1 题目1.2 笔记1.3 代码 02. 子集&#xff08;No. 78&#xff09;2.1 题目2.2 笔记2.3 代码 03. 子集 II&#xff08;No. 90&#xff09;3.1 题目3.2 笔记3.3 代码 Day 28 01. 复原 IP 地址&#xff08;No. 9…

SAP BC Partner XXXX:3299 not reached

带SAProuter 出现如下问题 不带SAProuer 正常登录 原因&#xff1a;SAProuter 服务未开启。 开启过程如下&#xff1a; 进入对应的SAProuter 目录 一般是SAProuter 服务器上 cmd进入目录 执行 saprouter.exe -r -V 2 -G saprouter.og -K "p:CNsap-router, OU0000725…