多线程同步安全机制

目录

以性能换安全 

1.synchronized 同步

(1)不同的对象竞争同一个资源(锁得住)

(2)不同的对象竞争不同的资源(锁不住)

(3)单例模式加锁

synchronized 同步块

2.JUC并发包

明锁机制

3.volatile关键字

可见性:

以空间(内存)换安全

1.线程本地变量:ThreadLocal


以性能换安全 

1.synchronized 同步

        synchronized 同步,也叫做暗锁,自动释放锁。

        锁得住的条件是:同一个对象(同一个资源)

        如果synchronized 修饰方法,就是给这个方法加锁。这时候如果有个线程被JVM调度执行,只有等这个线程执行完毕以后,这个线程才会自动释放锁,其它的线程才有机会执行。

(1)不同的对象竞争同一个资源(锁得住)

两个线程对象t1和t2竞争同一个UserRunnable对象

如下面的代码创建了两个线程,run()方法被synchronized修饰,只有等一个线程执行完run()方法并自动释放锁,另一个线程才能执行:

package com.sync;public class UserRunnable   implements  Runnable{@Overridepublic  synchronized void run() {// TODO Auto-generated method stubfor(int i=0;i<=10;i++){System.out.println(Thread.currentThread().getName()+",执行"+i);}}// 自动释放锁}
package com.sync;public class Test {public static void main(String[] args) {UserRunnable  ur  = new UserRunnable();  // 只有一个Runnable对象Thread  t1 =  new Thread(ur);Thread  t2 =  new Thread(ur);t1.start();t2.start();}}

运行结果:

(2)不同的对象竞争不同的资源(锁不住)

两个线程使用不同的UserThread对象,锁的是不同的对象

         两个线程使用不同的锁,没有竞争关系,所以无法实现同步机制。

package com.sync1;public class UserThread extends Thread{//synchronized是一个加锁操作//synchronized首先是对同一个资源(同一个对象)的加锁//锁得住的条件是:同一个对象public  synchronized void run(){for(int i=0;i<=10;i++){System.out.println(Thread.currentThread().getName()+",执行"+i);}}}
package com.sync1;public class Test {public static void main(String[] args) {UserThread  u1  = new UserThread();UserThread  u2  = new UserThread();u1.start();u2.start();}}

运行结果:

解决方法:

package com.sync1;public class Test {public static void main(String[] args) {//		UserThread  u1  = new UserThread();
//		UserThread  u2  = new UserThread();
//		
//		u1.start();
//		u2.start();UserThread  u1  = new UserThread();Thread  t1  = new Thread(u1);Thread  t2 = new Thread(u1);t1.start();t2.start();}}

(3)单例模式加锁

        使用synchronized给getInstance()方法加锁后,一个线程进入后立刻锁住,对象new完后自动解锁,此时对象已经创建完成,别的线程进入不用重新创建对象,所以两个线程返回的地址一样。

package com.sync2;public class User {private static  User  u ;private User(){}public synchronized static User  getInstance(){if(null == u){System.out.println(Thread.currentThread().getName()+"创建对象");u =  new User();}return u;}}
package com.sync2;public class UserThread1   extends Thread{public  void  run(){User  u1 =  User.getInstance();System.out.println(u1);}}
package com.sync2;public class UserThread2 extends Thread{public  void  run(){User  u2 =  User.getInstance();System.out.println(u2);}}
package com.sync2;public class Test {public static void main(String[] args) {UserThread1  u1 =  new UserThread1();UserThread2   u2 =  new UserThread2();u1.start();u2.start();}}

运行结果:

synchronized 同步块

synchronized(),()里面一定是引用类型对象,必须是同一个对象。

对需要竞争的代码进行锁定,降低锁定的范围,优化性能。

        下面是一个多线程银行账户操作模拟系统,包含:

  • 1个银行账户(Bank)
  • 3个支付平台线程(支付宝、微信、京东)
  • 使用同步机制保证账户操作的线程安全

1. Bank 类(共享资源)

package com.sync3;//银行类
public class Bank {// 卡号private String bankNumber = "";// 账户的金额private double money = 0.0;public Bank(String bankNumber, double money) {this.bankNumber = bankNumber;this.money = money;}//操作银行账户的方法 synchronized 修饰方法,会给整个方法加锁,导致整个方法被锁定,导致锁定的范围过大//synchronized 同步块,对需要竞争的代码进行锁定,降低锁定的范围,优化性能public   void  operatorBank(double  operatorMoney){System.out.println("欢迎您到银行办理具体的业务");synchronized(Bank.class)// ()里面一定是引用类型对象,必须是同一个对象{this.money += operatorMoney;System.out.println(Thread.currentThread().getName()+",操作的金额是:"+operatorMoney+",现在账户剩余的金额是:"+this.money);}System.out.println("谢谢您,欢迎下次光临");}}

2. 支付线程类

package com.sync3;public class JindongThread   extends Thread{double opMoney;Bank bank;public  JindongThread(String  threadName,double opMoney,Bank bank){super(threadName);this.opMoney = opMoney;this.bank  = bank;}public void run(){this.bank.operatorBank(this.opMoney);}
}
package com.sync3;public class WeixinThread  extends Thread{double opMoney;Bank bank;public  WeixinThread(String  threadName,double opMoney,Bank bank){super(threadName);this.opMoney = opMoney;this.bank  = bank;}public void run(){this.bank.operatorBank(this.opMoney);}}
package com.sync3;public class ZhifubaoThread   extends Thread{double opMoney;Bank bank;public  ZhifubaoThread(String  threadName,double opMoney,Bank bank){super(threadName);this.opMoney = opMoney;this.bank  = bank;}public void run(){this.bank.operatorBank(this.opMoney);}}

3.Test类

package com.sync3;/*** synchronized同步,就是加锁的操作,保证多线程竞争同一个资源时的安全。*/
public class Test {public static void main(String[] args) {Bank bank = new Bank("10086", 1000.0);ZhifubaoThread z = new ZhifubaoThread("支付宝", 300, bank);WeixinThread w = new WeixinThread("微信", -400, bank);JindongThread j = new JindongThread("京东", 600, bank);z.start();w.start();j.start();}}

运行结果:

2.JUC并发包

        JUC 是 java.util.concurrent 包及其子包(如 java.util.concurrent.atomic 和 java.util.concurrent.locks)的非官方但广为流传的缩写,全称是 Java Util Concurrent。它是 Java 标准库中为并发编程提供强大、高性能、线程安全的工具类的核心包。

明锁机制

  • 公平锁 (Fair Lock):new ReentrantLock(true);

    • 原则:遵循“先来后到”的公平原则。

    • 行为:当锁被释放时,会优先分配给等待时间最长的线程。就像现实中排队一样,先来的先获得服务。

    • 优点:所有线程都能得到执行机会,不会产生“饥饿”现象。

    • 缺点:性能开销较大。因为需要维护一个有序队列来管理线程,上下文切换更频繁。

  • 非公平锁 (Non-fair Lock):new ReentrantLock(false); 

    • 原则:允许“插队”。

    • 行为:当锁被释放时,所有正在尝试获取锁的线程(包括刚来和已经等待的)都会去竞争,谁抢到就是谁的。如果没抢到,才会被加入到等待队列的末尾。

    • 优点吞吐量高,性能更好。减少了线程切换的开销,充分利用了CPU时间片。

    • 缺点:可能导致某些线程长时间等待,永远拿不到锁(饥饿)。

package com.demo2;import java.util.concurrent.locks.Lock;public class Buy implements Runnable {Lock lock;private boolean flag = true;private int sum = 10;public Buy(Lock lock) {this.lock = lock;}@Overridepublic void run() {// TODO Auto-generated method stubwhile (flag) {lock.lock(); // 明锁try {Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + ",买到了票,是第" + this.sum-- + "张票");if (this.sum <= 1) {this.flag = false;}lock.unlock(); // 一定要手动释放锁} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
package com.demo2;//JUC并发包
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test {public static void main(String[] args) {//明锁机制   //new ReentrantLock(true); 是true是公平锁,公平的意思就是大家都有机会执行//new ReentrantLock(false); 是false是非公平锁,公非平的意思就是会有一个线程独占执行Lock  lock  =  new ReentrantLock(true);Buy  buy   = new Buy(lock);new Thread(buy,"张三线程").start();new Thread(buy,"李四线程").start();new Thread(buy,"王五线程").start();}}

公平锁运行结果:

非公平锁运行结果:

3.volatile关键字

  volatile是Java提供的一种轻量级的同步机制,用于确保变量的可见性和一致性。

可见性:
  • 当一个线程修改了volatile变量时,新值会立即被刷新到主内存

  • 其他线程读取该变量时,会强制从主内存重新读取最新值

  • 解决了线程间数据不可见的问题

不保证原子性:

  • volatile不能保证复合操作的原子性

  • 比如count++这样的操作(读取-修改-写入)不是原子性的

下面这段代码展示了volatile最经典的用法——作为状态标志位:

package com.volatiledemo;//volatile不能保证非原子操作的可见性和一致性
public class Test {public static void main(String[] args) {UserThread u =  new UserThread();u.start();try {Thread.sleep(5000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}u.setFlag(false);}}
package com.volatiledemo;public class UserThread   extends Thread{// 当flag声明为volatile时,主线程修改flag = false后,UserThread立即能看到这个变化private volatile boolean  flag  =true;private int a = 0;//private  boolean  flag  =true;public  void  run(){System.out.println(Thread.currentThread().getName()+",线程开始运行");while(flag){a =10;a++;}System.out.println(Thread.currentThread().getName()+",线程结束运行"+"a的值为:"+a);}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}}

运行结果:

以空间(内存)换安全

1.线程本地变量:ThreadLocal

        ThreadLocal是Java提供的线程局部变量,它为每个使用该变量的线程提供独立的变量副本,实现了线程间的数据隔离。

工作原理:

  • ThreadLocal内部使用ThreadLocalMap存储数据
  • 以当前线程作为key来存储和检索值
  • 每个线程都有自己独立的ThreadLocalMap

下面代码中虽然四个线程共享同一个UserRunnable实例,但由于使用了ThreadLocal:

  • 每个线程都有自己独立的User对象

  • 茉莉1线程设置的年龄不会影响栀子1线程的年龄值

  • 各线程的年龄值保持独立,互不干扰

package com.threadlocal;import java.util.Random;public class UserRunnable implements Runnable {// 线程本地变量  key-valueThreadLocal<User> userLocal = new ThreadLocal<User>();private User getUser() {// key:对象hascode()   value:对应这个对象   // 首先尝试从ThreadLocal获取User对象,每个User对象就是一个键值对User u = userLocal.get();// 如果不存在则创建新的User对象并存入ThreadLocal,确保每个线程有自己独立的User实例if (null == u) {u = new User();System.out.println(u);userLocal.set(u);}return u;}@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println(Thread.currentThread().getName() + ",执行run方法");Random r = new Random();int age = r.nextInt(100);System.out.println(Thread.currentThread().getName() + ",产生的随机数的年龄为:" + age);// 从ThreadLocal获取当前线程的User对象User u = this.getUser();u.setAge(age);try {Thread.sleep(2000);} catch (InterruptedException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}System.out.println(Thread.currentThread().getName() + ",before设置年龄的值为:" + u.getAge());try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ",after设置年龄的值为:" + u.getAge());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public static void main(String[] args) {UserRunnable u = new UserRunnable();Thread s1 = new Thread(u, "茉莉1");Thread s2 = new Thread(u, "栀子1");Thread s3 = new Thread(u, "茉莉2");Thread s4 = new Thread(u, "栀子2");s1.start();s2.start();s3.start();s4.start();}}
package com.threadlocal;public class User {private  int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}}

运行结果:

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

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

相关文章

多路复用 I/O 函数——`select`函数

好的&#xff0c;我们以 Linux 中经典的多路复用 I/O 函数——select 为例&#xff0c;进行一次完整、深入且包含全部代码的解析。 <摘要> select 是 Unix/Linux 系统中传统的多路复用 I/O 系统调用。它允许一个程序同时监视多个文件描述符&#xff08;通常是套接字&…

嵌入式碎片知识总结(二)

1.repo的一个问题&#xff1a;repo init -u ssh://shchengerrit.bouffalolab.com:29418/bouffalo/manifest/bouffalo_sdk -b master -m allchips-internal.xml /usr/bin/repo:681: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in…

java中二维数组笔记

课程链接:黑马程序员java零基础[上] 1.二维数组的内存分布 在 Java 中&#xff0c;二维数组并不是一整块连续的二维空间&#xff0c;而是数组的数组。具体而言,在声明一个二维数组&#xff1a;如int[][] arr new int[2][3];时&#xff0c;内存中会发生如下: 1.1 栈上的引用变…

系统架构设计师备考第13天——计算机语言-多媒体

一、多媒体基础概念媒体的分类 感觉媒体&#xff1a;人类感官直接接收的信息形式&#xff08;如声音、图像&#xff09;。表示媒体&#xff1a;信息的数字化表示&#xff08;如JPEG图像、MP3音频&#xff09;。显示媒体&#xff1a;输入/输出设备&#xff08;如键盘、显示器&am…

指针高级(1)

1.指针的运算2.指针运算有意义的操作和无意义的操作、#include <stdio.h> int main() {//前提条件&#xff1a;保证内存空间是连续的//数组int arr[] { 1,2,3,4,5,6,7,8,9,10 };//获取0索引的内存地址int* p1 &arr[0];//通过内存地址&#xff08;指针P&#xff09;…

【可信数据空间-Trusted Data Space综合设计方案】

可信数据空间-Trusted Data Space综合设计方案 一.简介与核心概念 1.什么是可信数据空间 2.核心特征 3.主要应用场景 二、 产品设计 1. 产品定位 2. 目标用户 3. 核心功能模块 a. 身份与访问管理 b. 数据目录与服务发现 c. 策略执行与合约管理 d. 数据连接与计算 e. 审计与溯源…

技术方案之Mysql部署架构

一、序言在后端系统中&#xff0c;MySQL 作为最常用的关系型数据库&#xff0c;其部署架构直接决定了业务的稳定性、可用性和扩展性。你是否遇到过这些问题&#xff1a;单机 MySQL 突然宕机导致业务中断几小时&#xff1f;高峰期数据库压力过大&#xff0c;查询延迟飙升影响用户…

js语言编写科技风格博客网站-详细源码

<!-- 科技风格博客网站完整源码 --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <ti…

AI如何理解PDF中的表格和图片?

AI的重要性已渗透到社会、经济、科技、生活等几乎所有领域&#xff0c;其核心价值在于突破人类能力的物理与认知边界&#xff0c;通过数据驱动的自动化、智能化与优化&#xff0c;解决复杂问题、提升效率并创造全新可能性。从宏观的产业变革到微观的个人生活&#xff0c;AI 正在…

Graphpad Prism 实战教程(一):小鼠体重变化曲线绘制全流程(含数据处理与图表美化)

在药理实验、动物模型构建等科研场景中,小鼠体重变化数据是评估实验干预效果(如药物安全性、疾病进展影响)的核心指标之一。将零散的体重数据转化为直观的折线图,不仅能清晰呈现体重随时间的波动趋势,更是后续结果解读与论文图表呈现的关键步骤。本文将从 Excel 数据整理开…

计算机视觉(六):腐蚀操作

腐蚀&#xff08;Erosion&#xff09;是计算机视觉和图像处理中一种基础且至关重要的形态学操作。它与膨胀&#xff08;Dilation&#xff09;互为对偶&#xff0c;共同构成了形态学处理的基石。腐蚀操作主要用于缩小前景物体的面积&#xff0c;去除图像中的噪声&#xff0c;以及…

AI随笔番外 · 猫猫狐狐的尾巴式技术分享

&#x1f380;【开场 咱才不是偷懒写博客】&#x1f43e;猫猫趴在键盘边&#xff0c;耳朵一抖一抖&#xff1a;“呜呜呜……明明说好要写技术总结&#xff0c;结果咱脑袋里全是尾巴……要不今天就水一篇随意的 AI 技术分享算啦&#xff1f;”&#x1f98a;狐狐把书卷轻轻放在桌…

数据分析与挖掘工程师学习规划

一、数学与统计学基础概率论与数理统计随机变量、概率分布&#xff08;正态分布、泊松分布等&#xff09;、大数定律、中心极限定理假设检验、置信区间、方差分析&#xff08;ANOVA&#xff09;、回归分析贝叶斯定理及其在分类问题中的应用&#xff08;如朴素贝叶斯算法&#x…

(线上问题排查)4.CPU使用率飙升:从应急灭火到根因治理

目录 从宏观到微观&#xff1a;CPU排查的“破案”流程 第一阶段&#xff1a;应急响应——找到“谁”在捣乱 1. 全局视角&#xff1a;top命令的初窥 2. 进程内窥视&#xff1a;揪出问题线程 第二阶段&#xff1a;深入分析——理解“为什么” 3. 线程堆栈分析&#xff1a;查…

如何快速实现实时云渲染云推流平台的网络环境配置与端口映射

LarkXR是由Paraverse平行云自主研发的实时云渲染推流平台&#xff0c;以其卓越的性能和丰富完备的功能插件&#xff0c;引领3D/XR云化行业风向标。LarkXR适用于3D/XR开发者、设计师、终端用户等创新用户&#xff0c;可以在零硬件负担下&#xff0c;轻松实现超高清低时延的3D交互…

13、Docker构建镜像之Dockerfile

13、Docker构建镜像之Dockerfile 1、Dockerfile是什么 Dockerfile是Docker镜像的构建文件&#xff0c;它包含了一系列指令和参数&#xff0c;用于定义如何构建一个Docker镜像。通过Dockerfile&#xff0c;我们可以将应用程序和其依赖的组件打包到一个独立的镜像中&#xff0c;方…

TensorFlow 深度学习 | 三种创建模型的 API

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 TensorFlow 深度学习 | 三种创建模型的 API 在 TensorFlow 中,模型的构建方式非常灵…

LeetCode82删除排序链表中的重复元素 II

文章目录删除排序链表中的重复元素 II题目描述示例核心思想最优雅解法算法步骤详解示例1演示&#xff1a;[1,2,3,3,4,4,5]关键理解点1. 虚拟头节点的作用2. 重复检测逻辑3. 完全删除重复节点边界情况处理情况1&#xff1a;空链表情况2&#xff1a;单节点情况3&#xff1a;全部重…

蓝桥杯算法之基础知识(6)

目录 Ⅰ.os操作 Ⅱ.时间库&#xff08;很重要&#xff09; Ⅲ.基本单位换算&#xff08;ms&#xff0c;min&#xff0c;h的单位换算&#xff09; Ⅳ.时间戳 Ⅴ.文件读取 Ⅵ.堆 Ⅶ.math操作 Ⅷ.range&#xff08;&#xff09;方法单独使用 Ⅸ.python 的异常输出 Ⅹ.for…

多架构/系统图,搞懂:期货账户体系,太通透了!

Hi,围炉喝茶聊产品的新老朋友好!上周和大家聊了国内6大期货交易所清算交收,感兴趣的话烦请戳蓝色链接去学习,就当为下面学习作知识铺垫,更重要是温故知新,并保持知识连贯性。另外围炉特意整理了与账户相关的文章,如下所示: “保证金被扣”拆解期货交易所:清算交收体系…