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

线程同步

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.结果

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

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

相关文章

Python 基于 AI 动物识别技术的研究与实现,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

hope实验室预备役第三次测试题解

目录 1.选数 2.奇怪的电梯 3.无线通讯网 4. Rotate Colored Subsequence 5.LOWER 6.Error Correction 1.选数 P1036 [NOIP2002 普及组] 选数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 已知 n 个整数 1,2,⋯ ,x1​,x2​,⋯,xn​&#xff0c;以及 1 个整…

VNCTF 2024 Web方向 WP

Checkin 题目描述&#xff1a;Welcome to VNCTF 2024~ long time no see. 开题&#xff0c;是前端小游戏 源码里面发现一个16进制编码字符串 解码后是flag CutePath 题目描述&#xff1a;源自一次现实渗透 开题 当前页面没啥好看的&#xff0c;先爆破密码登录试试。爆破无果…

洗地机什么牌子最好?家用洗地机推荐

如今洗地机已经在家庭中扮演着至关重要的角色&#xff0c;随着人们对居住环境的卫生要求越来越高&#xff0c;洗地机作为结合了吸尘和拖地为一体的清洁工具&#xff0c;不仅可以高效的帮助我们清洁地板&#xff0c;节省时间&#xff0c;还可以为我们节省很多收纳空间。那么&…

typeScript 类型推论

什么是类型推论&#xff1f; 类型推论是 TypeScript 中的一个特性&#xff0c;它允许开发人员不必显式地指定变量的类型。相反&#xff0c;开发人员可以根据变量的使用情况让 TypeScript 编译器自动推断出类型。例如&#xff0c;如果开发人员将一个字符串赋值给一个变量&#…

【力扣白嫖日记】1795.每个产品在不同商店的价格

前言 练习sql语句&#xff0c;所有题目来自于力扣&#xff08;https://leetcode.cn/problemset/database/&#xff09;的免费数据库练习题。 今日题目&#xff1a; 1795.每个产品在不同商店的价格 表&#xff1a;Products 列名类型product_idintstore1intstore2intstore3in…

项目中和兄弟部门难以高效协作?你需要注意这四点

在组织架构日益复杂的今天&#xff0c;靠一个人单打独斗完成工作或项目越来越难&#xff0c;也越来越不可能。不知你是否留意过&#xff0c;无论招聘什么岗位&#xff0c;几乎所有企业都在强调“团队合作”。 这里的团队不光指的是同部门协作&#xff0c;要包括公司内部的跨部门…

网络原理 - HTTP/HTTPS(1)

HTTP HTTP是什么 HTTP("全程超文本协议")是一种应用非常广泛的应用层协议. 文本:字符串(能在utf8/gbk)码表上找到合法字符. 超文本:不仅是字符串,还能携带图片啥的(HTML). 富文本:类似于word文档这种. HTTP诞生于1991年.目前已经发展为最主流使用的一种应用层协议.…

不等式的证明之二

不等式的证明之二 证明下述不等式证法一证法二证法二的补充 证明下述不等式 设 a , b , c a,b,c a,b,c 是正实数&#xff0c;请证明下述不等式&#xff1a; 11 a 5 a 6 b 11 b 5 b 6 c 11 c 5 c 6 a ≤ 3 \begin{align} \sqrt{\frac{11a}{5a6b}}\sqrt{\frac{11b}{5b6c}…

centos7如何切换到root用户

在 CentOS 7 中&#xff0c;你可以通过几种方式切换到 root 用户。最常用的方法是使用 su (switch user) 命令或者 sudo 命令。这里是如何使用这些命令的详细说明&#xff1a; 使用 su 命令 打开终端。输入以下命令并按下回车键&#xff1a;su -系统会提示你输入 root 用户的…

云手机在引流方面有什么优势?

对于电商商家而言&#xff0c;无论是在亚马逊还是其他平台&#xff0c;有效的流量来源主要集中在短视频引流和社交电商营销。要在新兴社交平台为企业电商带来更多流量&#xff0c;不可忽视云手机的关键作用和独特优势。 云手机的定义与作用 在经营TikTok、Facebook和INS账号时&…

外汇110:外汇做空是什么意思?如何运作?一文读懂

外汇市场允许卖空&#xff0c;就像众多金融市场一样。但什么是卖空呢&#xff1f;如何外汇做空&#xff1f;在本文中&#xff0c;我们将讨论如何做空货币。什么是外汇做空&#xff1f; 外汇做空&#xff08;Short Selling&#xff09;是外汇市场上的一种投资方式。它指的是投资…

Java面向对象案例之设计用户去ATM机存款取款(三)

需求及思路分析 业务代码需求&#xff1a; 某公司要开发“银行管理系统”&#xff0c;请使用面向对象的思想&#xff0c;设计银行的储户信息&#xff0c;描述存款、取款业务。 储户类的思路分析&#xff1a; 属性&#xff1a;用户姓名、密码、身份证号、账号、帐户余额 方法&a…

vue生命周期函数

父子组件加载顺序 加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted子组件更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated父组件更新过程 父beforeU…

JS画布内生成图标,并实现拖拽,连线,刷新

JS有现成的拖拽命令&#xff0c;但是只能实现简单的拖拽功能&#xff0c;下面演示的可以在画布的任意一个地方拖拽&#xff0c;并停留在画布的任意地方。 整个框架代码如下&#xff1a; <html> <head><meta charset"UTF-8"><title>拖拽放置…

【详解】图的概念和存储结构(邻接矩阵,邻接表)

目录 图的基本概念&#xff1a; 图的存储结构 邻接矩阵&#xff08;GraphByMatrix&#xff09;&#xff1a; 基本参数&#xff1a; 初始化&#xff1a; 获取顶点元素在其数组中的下标 &#xff1a; 添加边和权重&#xff1a; 获取顶点的度&#xff1a; 打印图&#xf…

动态代理IP如何选择?

IP地址是由IP协议所提供的一种统一的地址格式&#xff0c;通过为每一个网络和每一台主机分配逻辑地址的方式来屏蔽物理地址的差异。根据IP地址的分配方式&#xff0c;IP可以分为动态IP与静态IP两种。对于大部分用户而言&#xff0c;日常使用的IP地址均为动态IP地址。从代理IP的…

CCF-A类VLDB’24 3月1日截稿!数据界的璀璨盛会等你投稿!

会议之眼 快讯 第50届VLDB( International Conference on Very Large Databases)即超大型数据库国际会议将于 2024 年 8月25日至29日在中国广州朗廷广场隆重举行&#xff01;VLDB大会是数据库领域的顶级学术盛会&#xff0c;而SIGMOD和ICDE则是与之齐名的另外两大数据库会议。这…

Android电量相关知识

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、 查看耗电情况3.1 注册广播 ACTION…

HBuilderX 插件开发指南(一):从插件开发到发布的完整流程

前端目前主流使用的IDE工具有VS Code、Sublime Text3、HBuilder X等等 本期我们主要了解HBuilder X&#xff0c;作为前端通用型开发工具&#xff0c;拥有可视化的操作方式&#xff0c;内置相关环境&#xff0c;开箱即用&#xff0c;无需配置nodejs等优点外&#xff0c;对uni-a…