java多线程同步的实现方式

2023-11-15

1.什么时候会出现线程安全问题

当多个线程同时操作同一个共享资源时就会出现线程安全问题,将会导致数据不一致。因此,需要使用一些手段来保证共享资源的准确性。下面以A、B、C三个小盆友吃苹果为例子,演示下线程不安全的情况。

package com.wk.concurrent;

class Apple implements Runnable {
    private int num = 50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            eat();
        }
    }

    public void eat() {
        if (num > 0) {
            try {
                //导致一个资源信息被多个用户同时拿到
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "吃了编号为" + num + "的苹果");
            num--;
        }
    }
}

public class ImplementDemo {
    public static void main(String[] args) {
        Apple a = new Apple();
        new Thread(a, "A").start();
        new Thread(a, "B").start();
        new Thread(a, "C").start();
    }
}


A、B、C三个小朋友都同时吃了编号为50的苹果,显然是不对的。

2.使用synchronized关键字

用synchronized关键字修饰方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

2.1修饰方法

 public synchronized void eat() {
        if (num > 0) {
            try {
                //导致一个资源信息被多个用户同时拿到
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "吃了编号为" + num + "的苹果");
            num--;
        }
    }

2.2 修饰代码块

 public void eat() {
        synchronized (this) {
            if (num > 0) {
                try {
                    //导致一个资源信息被多个用户同时拿到
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "吃了编号为" + num + "的苹果");
                num--;
            }
        }
    }

3.使用重入锁实现线程同步

在JavaSE5.0中新增了一个java.util.concurrent包,juc包是专为多线程和高并发来设计的。其中ReentrantLock(可重入锁)类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
下面以银行存取款为例,演示一下ReentrantLock的用法

package com.wk.concurrent;

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

public class SynchronizedThread {

    class Bank {

        private int account = 100;
        //声明锁
        private Lock lock = new ReentrantLock();

        public int getAccount() {
            return account;
        }

        public void save(int money) {
            lock.lock();
            try {
                account += money;
            } finally {
                lock.unlock();
            }

        }
    }

    class NewThread implements Runnable {
        private Bank bank;

        public NewThread(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                // bank.save1(10);
                bank.save(10);
                System.out.println(i + "账户余额为:" + bank.getAccount());
            }
        }

    }

    /**
     * 建立线程,调用内部类
     */
    public void useThread() {
        Bank bank = new Bank();
        NewThread new_thread = new NewThread(bank);
        System.out.println("线程1");
        Thread thread1 = new Thread(new_thread);
        thread1.start();
        System.out.println("线程2");
        Thread thread2 = new Thread(new_thread);
        thread2.start();
        Thread thread3 = new Thread(new_thread);
        thread3.start();
    }

    public static void main(String[] args) {
        SynchronizedThread st = new SynchronizedThread();
        st.useThread();
    }

}

4.wait与notify方法

以生产者-消费者模型为例:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。
wait()和notify()方法是Object类中的两个方法。除了使用synchronized关键字来协调线程之间的执行状态,还可以使用这两个方法以另外一种方式来协调线程。这两个方法是非静态的,因为这两个方法是Object类中的方法,所以Java中所有的实例都可以调用这两个方法。
wait()方法:它让执行此代码的线程进入挂起状态。如果在处于挂起状态时,因为某些原因挂起被打断了,那么该方法就会抛出一个InterruptedException的异常,这个异常和sleep方法抛出的异常是同一个类型。
notify()方法:唤醒因为在同一个对象上调用wait()而处于挂起状态的线程,让线程可以继续执行下去。
下面的代码,是一个生产者和消费者模型的代码:

package com.wk.concurrent;

import java.util.PriorityQueue;

public class ProducerAndConsumer {
    private int queueSize = 10;
    private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);

    public static void main(String[] args) {
        ProducerAndConsumer test = new ProducerAndConsumer();
        Producer producer = test.new Producer();
        Consumer consumer = test.new Consumer();

        producer.start();
        consumer.start();
    }

    class Consumer extends Thread {

        @Override
        public void run() {
            consume();
        }

        private void consume() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == 0) {
                        try {
                            System.out.println("队列空,等待数据");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.poll(); //每次移走队首元素
                    queue.notify();
                    System.out.println("从队列取走一个元素,队列剩余" + queue.size() + "个元素");
                }
            }
        }
    }

    class Producer extends Thread {

        @Override
        public void run() {
            produce();
        }

        private void produce() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == queueSize) {
                        try {
                            System.out.println("队列满,等待有空余空间");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    queue.offer(1);
                    queue.notify();
                    System.out.println("向队列取中插入一个元素,队列剩余空间:" + (queueSize - queue.size()));
                }
            }
        }
    }
}

5.使用原子变量实现线程同步

原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作,即这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类。
还是以Bank存取钱为例子,只需替换章节3中Bank类即可:

class Bank {
        private AtomicInteger account = new AtomicInteger(100);
        public AtomicInteger getAccount() {
            return account;
        }
        public void save(int money) {
            account.addAndGet(money);
        }
    }

关于volatile关键字

volatile只有如下作用:
1.保证内存可见性
2.禁止指令重排序
在网上看到很多博文说,volatile关键字能够保证多线程的同步,这是错误的。尽管volatile关键字可以保证内存可见性和有序性,但不能保证原子性

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java多线程同步的实现方式 的相关文章

  • 使用 objectGUID 进行查询 - Spring LDAP 模板

    我正在尝试获取 存储并依次使用 objectGUID 来查询 Active Directory 为了获取用户属性我正在使用以下 public static class MyDnKeyValueAttMapper implements Att
  • 如何获取枚举的子集

    大多数情况下 包含所有元素的枚举显示在用户界面的下拉列表中 我们只需要在用户界面中显示 5 个字段中的 2 个 通过某种方式利用可用于枚举的相同函数来获取此数据的更简单方法是什么 enum Color RED GREEN BLACK BLU
  • 如何将 .cer 证书导入 java 密钥库?

    在开发 Java Web 服务客户端期间 我遇到了一个问题 Web 服务的身份验证使用客户端证书 用户名和密码 我从网络服务背后的公司收到的客户端证书位于 cer格式 当我使用文本编辑器检查该文件时 它具有以下内容 BEGIN CERTIF
  • 带有来自 Selenium 2 / WebDriver 的 Id 的 jQuery 元素选择器

    我可以在 Selenium 中获取元素的 ID RemoteWebElement webElement getId 它返回一个像这样的字符串 e9b6a1cc bb6f 4740 b9cb b83c1569d96d 我想知道这个ID的来源
  • 使用 xuggle 将 mp3 转换为 wav 出现异常

    我正在尝试将 mp3 转换为 wav 代码在这里 String mp3 F work pic2talk38512 mp3 String wav F work pic2talk38512 wav TranscodeAudioAndVideo
  • 从 Bitmap 类创建 .bmp 图像文件

    我创建了一个使用套接字的应用程序 客户端在其中接收图像并将图像数据存储在 Bitmap 类中 谁能告诉我如何创建一个名为我的图像 png or 我的图像 bmp来自此 Bitmap 对象 String base64Code dataInpu
  • Eclipse 自动完成更改变量名称

    只是一个愚蠢的问题 但很难搜索 因为有很多关于 Eclipse 自动完成的主题 而且很难找到与我的问题匹配的内容 所以问题是 如果我写 MyClass MyVarName 然后按空格键 添加 new MyClass Eclipse 自动添加
  • 使用 java 的 RAR 档案 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Java 9 中可以使用提前编译吗?

    As per JEP 295 http openjdk java net jeps 295 任何 JDK 模块 类或用户代码的 AOT 编译都是实验性的 JDK 9 中不支持 要使用 AOT 化的 java base 模块 用户必须编译该模
  • 将 JSON Map 传递到 Spring MVC 控制器

    我正在尝试将 Map 的 JSON 表示形式作为 POST 参数发送到我的控制器中 RequestMapping value search do method RequestMethod GET consumes application j
  • JPA:如何将字符串持久保存到数据库字段中,输入 MYSQL Text

    需求是用户可以写文章 所以我选择typeText为了contentmysql数据库内的字段 我怎样才能转换Java String into MySQL Text 干得好Jim Tough Entity public class Articl
  • Java byte[] 与 String 之间的转换

    为什么这个junit测试失败了 import org junit Assert import org junit Test import java io UnsupportedEncodingException public class T
  • 适用于 Solaris 的 Java 8 中缺少 javaws

    看起来 Oracle 从 Java 8 for Solaris 中删除了 Java Web Start javaws 在 Java 8u51 中不再可用 来自兼容性指南 http www oracle com technetwork jav
  • Spring 术语中命令、表单、业务和实体对象之间的区别?

    我试图理解这些对象在松散耦合系统方面的差异 业务对象与实体对象相同吗 我可以使用 MVC 中的业务或实体对象作为我的命令对象吗 命令对象与表单对象相同吗 只是寻找 Spring 术语和用法中对象类型的说明 我在 stackoverflow
  • 使用 CrudRepository 进行自定义查询

    我想使用 CrudRepository 自定义查询 这是我的代码 Repository public interface CustomerRepository extends CrudRepository
  • Java中无参数的for循环

    我在看别人的代码 发现了这段代码 for 我不是 Java 专家 这行代码在做什么 起初 我认为这会创建一个无限循环 但在该程序员使用的同一个类中 while true 其中 如果我错了 请纠正我 是一个无限循环 这两个相同吗 为什么有人会
  • 使用 include 进行 JAXB 剧集编译不起作用

    我有 2 个模式 A B 我在 B 中重用了一些 A 元素 我不使用命名空间 我在用着
  • 多对多不检索映射数据

    Spring boot 2 5 6 我无法安装版本 概要文件 java Getter Setter NoArgsConstructor AllArgsConstructor EqualsAndHashCode FieldDefaults l
  • Apache Kafka 是否提供异步订阅回调 API?

    我的项目正在将 Apache Kafka 视为老化的基于 JMS 的消息传递方法的潜在替代品 为了让这个过渡尽可能的顺利 如果替代的排队系统 Kafka 有一个异步订阅机制那就更理想了 类似于我们当前项目使用的JMS机制MessageLis
  • JSP 和 scriptlet

    我知道现在使用 scriptlet 被认为是禁忌 没关系 我会同意Top Star的话 因为我目前只是Java新手 到目前为止我听到的是 它是为了让设计师的生活更轻松 但我想知道 这是否与JSP页面的性能有关 另一方面 如果只是为了 让设计

随机推荐

  • 参与 2023 第一季度官方 Flutter 开发者调查

    Flutter 3 7 已经正式发布 每个季度一次的 Flutter 开发者调查也如约而至 邀请社区的各位成员们填写 调查表链接 https flutter cn urls 2023q1wx 本次调研将会涉及既有的对 Flutter 整体和
  • 【李宏毅深度强化学习笔记】—7、Sparse Reward

    原文链接 https blog csdn net ACL lihan article details 104103873 李宏毅深度强化学习笔记 1 策略梯度方法 Policy Gradient 李宏毅深度强化学习笔记 2 Proximal
  • 24个K8S常用场景使用命令(推荐收藏)!

    kubectl是K8S中的一个命令行工具 主要用于管理和操作K8S集群 kubectl通过向K8S API发送REST请求 允许用户与K8S集群中的各种资源进行交互 列如Pod service Deployment等 kubectl提供了一
  • 【数据结构学习笔记】18:线段树(建树、单点修改、区间查询)

    1 线段树上的操作 push up int u 由子节点的信息去计算父节点的信息 例如两个子节点的区间和 加起来就是父节点表示的区间和 其中u是当前节点编号 表示用u的左右两个子节点来算一下自己这个节点的信息 push down 将父节点的
  • 数据爆炸,Python一键获取阿里法拍的爆款商品数据,并保存到数据库!

    目录 前言 获取数据代码实现 步骤1 获取目标网址 步骤2 向目标网址发送请求并获取响应内容 步骤3 解析网页内容并提取商品信息 步骤4 将商品信息保存到DataFrame中 将商品信息保存到数据库中 步骤1 安装MySQL Connect
  • Spring Boot的自动配置与自定义配置(附配置优先级表)

    相比于Spring MVC Spring Boot省去了繁琐的配置 提供了大部分场景下的默认配置 用户可以在不做任何配置的情况下使用Spring Boot框架进行开发 如果默认的参数并不能满足用户的需求 也只需创建一个配置文件并加上自定义的
  • JetBrains Rider 连接MySQL失败 解决方案

    JetBrains Rider 连接MySQL失败 解决方案 解决JetBrains Rider连接数据库失败 解决方案 设置MySQL时区 time zone 错误界面 Rider 连接mysql 用户名 密码 Port 全都配置好了 点
  • _萌新 web3

  • QFile清空原来文件内容的方法

    QFile清空原来文件内容的方法 Qt 清空文件方法 Qt 清空文件方法 方法一 void DataOperate clearFileInfos QString fileName QFile file fileName file resiz
  • LeetCode1823.找出游戏的胜利者

    共有 n 名小伙伴一起做游戏 小伙伴们围成一圈 按 顺时针顺序 从 1 到 n 编号 确切地说 从第 i 名小伙伴顺时针移动一位会到达第 i 1 名小伙伴的位置 其中 1 lt i lt n 从第 n 名小伙伴顺时针移动一位会回到第 1 名
  • 机器学习之加州房价预测(一)

    加州房价预测实例 任务 基于加州房价数据集建立一个预测模型 使之可以在给定的条件下 预测加州任何地点的房价的中位数 一 定义问题 1 公司要如何利用我的模型 模型的输出将作为另一个机器学习算法的输入 该算法在综合考虑其他因素之后 决定是否值
  • 推荐一本书——《The Scientist and Engineer's Guide to Digital Signal Processing》

    突然在国外的网站上看到一本非常好的数字信号处理的书籍 讲解简介明白 清晰易懂 书籍为免费电子版 地址为 http www dspguide com pdfbook htm
  • day05-编程题

    知识点 方法 题目1 训练 定义一个方法 该方法能够找出两个小数中的较小值并返回 在主方法中调用方法进行测试 训练提示 根据方法的功能描述 方法的参数应该是两个小数 要返回两个小数的较小值 所以返回值类型也是小数类型 解题方案 操作步骤 定
  • QT中学习Opengl---(GLSL简单的使用)

    前言 本文的代码是 LearnOpenGL 中对应代码 这里提供学习 大家喜欢的可去官方网站去看看 https learnopengl cn readthedocs io zh latest https learnopengl cn rea
  • C++的模板特例化template<>

    C 的模板特例化是指当我们定义了一个通用的模板类或模板函数时 如果特定输入参数类型或值需要进行不同的处理 我们可以为这些特定情况提供单独的实现 这就是模板特例化 下面我们将详细介绍C 的模板特例化 假设我们有以下的一个模板类 templat
  • java自学笔记12:java中的集合框架(下)List

    一 学生选课 判断List中课程是否存在 思考 在课程序列中 如何判断是否包含某门或者某几门课程 如果课程序列包含某门课程 如何判断该课程的索引位置 在学生映射表中 如何判断是否包含某个学生ID 又该如何判断是否包含某个学生对象 如果想把课
  • 解读随着教育改革的深入steam教育

    STEAM鼓励孩子勇于创新和探索 打破思维的第三面墙 自古以来 大家都是教育孩子纠正错误 而STEAM可以让孩子们通过与小组实践学习 探索讨论 交流思想和相互帮助 来发现自己的缺点和不足 通过团队合作来弥补自己的劣势 可以说 STEAM是一
  • Pandas 返回Nan值的行索

    Pandas 返回Nan值的行索 通过np where函数查找 gt gt gt df Out 1 0 1 0 0 450319 0 062595 1 0 673058 0 156073 2 0 871179 0 118575 3 0 59
  • Mysql大小写敏感设置(Docker版)

    应用场景 本人由于项目前期使用windows版国产数据库开发 默认就是大小写不敏感的 加上代码规范约束不够 导致代码中SQL大小写不统一 后期有需求要更换数据库 改用Mysql 因为在Linux系统中Mysql默认是大小写敏感的 所以需要对
  • java多线程同步的实现方式

    java多线程同步的实现方式 1 什么时候会出现线程安全问题 2 使用synchronized关键字 2 1修饰方法 2 2 修饰代码块 3 使用重入锁实现线程同步 4 wait与notify方法 5 使用原子变量实现线程同步 关于vola