Java基础知识总结(78)

/**

 * 线程加锁

   */

   public class SynchronizedDemo2 {

   //静态成员变量 在主内存中

   static int i;

   //静态成员方法

   public static void add(){

       synchronized (SynchronizedDemo2.class){

           i++;

       }

   }

   public static void main(String[] args) throws InterruptedException {

       Thread t1 = new Thread(()->{

           for (int j = 0; j < 100; j++) {

               add();

           }

       });

       Thread t2 = new Thread(()->{

           for (int j = 0; j < 100; j++) {

               add();

           }

       });

       //启动t1线程

       t1.start();

       //启动t2线程

       t2.start();

       //休眠main线程,让其他线程优先执行

       TimeUnit.SECONDS.sleep(1);

       //加锁后 不会出现值不同步的情况 每次运行结果都是200

       System.out.println(i);

   }

   }

   加锁后以上程序可能出现的执行过程

               t1线程                              t2线程

   时刻1  抢锁且成功                                    为就绪态

   时刻2   从主内存中复制共享变量到t1线程的私有内存中      为就绪态

   时刻3   对私有内存中的变量进行逻辑处理                     为就绪态

   时刻4   时间片到                                         运行状态

   时刻5    就绪状态                                       抢时间片,但是没有抢到,该线程处于阻塞状态, 直到时间片结束

   时刻6  时间片到,运行状态,将处理后的结果写入主内存中      就绪

   时刻7    退出临界区,释放锁                              抢时间片,并且抢到时间片,开始执行

/**

 * 如果多个线程分别持有自己的锁,则加锁没有意义

 * 锁应是唯一的,体现互斥性

   */

   public class SynchronizedDemo3 {

   //静态成员变量 在主内存中

   static int i;

   //静态成员方法

   public static void add(){

       i++;

   }

   public static void main(String[] args) throws InterruptedException {

       //注意,锁可以是任意对象

       //定义两个锁

       Object lock1 = new Object();

       Object lock2 = new Object();

       Thread t1 = new Thread(()->{

           for (int i = 0; i < 100; i++) {

               synchronized(lock1){

                   //为某个具体操作加锁 而不是所有代码

                   add();

               }

           }

       });

       Thread t2 = new Thread(()->{

           for (int i = 0; i < 100; i++) {

               synchronized(lock2){

                   add();

               }

           }

       });

       //启动t1线程

       t1.start();

       //启动t2线程

       t2.start();

       //休眠main线程,让其他线程优先执行

       TimeUnit.SECONDS.sleep(1);

       System.out.println(i);

   }

   }


 

/**

 * 证明线程处于休眠状态时,不会释放锁。

   */

   public class SynchronizedDemo4 {

   public static void main(String[] args) throws InterruptedException {

       //注意,锁可以是任意对象

       Object lock = new Object();

       Thread t1 = new Thread(()->{

           synchronized(lock){

               System.out.println("t1线程获得锁");

               try {

                   System.out.println("t1线程开始休眠");

                   TimeUnit.SECONDS.sleep(10);

                   System.out.println("t1线程结束休眠");

               } catch (InterruptedException e) {

                   throw new RuntimeException(e);

               }

           }

       });

       Thread t2 = new Thread(()->{

           synchronized(lock){

               Thread thread = Thread.currentThread();

               System.out.println("线程t2获得锁");

               for (int i = 0; i < 50; i++) {

                   //执行t2线程的条件是 线程2抢占锁成功 并且分配到时间片

                   //若在t1线程休眠过程中 t2线程执行该语句 则说明线程在休眠过程中会释放锁,反之则不会

                   System.out.println(thread.getName()+"**********"+i);

               }

           }

           System.out.println("线程t2释放锁");

       });

       //启动t1线程

       t1.start();

       TimeUnit.SECONDS.sleep(1);

       //启动t2线程

       t2.start();

       //休眠main线程

       TimeUnit.SECONDS.sleep(3);

       //获取t2的状态

       System.out.println(t2.getState());//block

       //结论:线程在休眠过程中不会释放锁

   }

   }


 

        synchronized修饰类方法时,锁时当前类对象即类名.class,当synchronized修饰实例方法时,锁时当前对象即this。

/**

 * synchronized修饰类方法 当前类对象作为锁

   */

   public class SynchronizedDemo5 {

   //静态成员变量 在主内存中

   static int i;

   //静态成员方法

   public synchronized static void add(){

       i++;

   }

   public static void main(String[] args) throws InterruptedException {

       Thread t1 = new Thread(()->{

           for (int i = 0; i < 100; i++) {

               add();

           }

       });

       Thread t2 = new Thread(()->{

           for (int i = 0; i < 100; i++) {

               add();

           }

       });

       //启动t1线程

       t1.start();

       //启动t2线程

       t2.start();

       //休眠main线程,让其他线程优先执行

       TimeUnit.SECONDS.sleep(1);

       System.out.println(i);

   }

   }

/**

 * synchronized修饰实例方法 线程对应的对象作为锁对象

 * 不存在锁竞争,因此在临界区也不会存在互斥性

   */

   public class SynchronizedDemo6 {

   static int i = 0;

   public static void add(){

       i++;

   }

   public static void main(String[] args) throws InterruptedException {

       Thread t1 = new Thread(new Runnable() {

           @Override

           public synchronized void run() {

               for (int i = 0; i < 100; i++) {

                   add();

               }

           }

       }

       );

       Thread t2 = new Thread(new Runnable() {

           @Override

           public synchronized void run() {

               for (int i= 0; i < 100; i++) {

                   add();

               }

           }

       }

       );

       //启动t1线程

       t1.start();

       //启动t2线程

       t2.start();

       //休眠main线程,让其他线程优先执行

       TimeUnit.SECONDS.sleep(1);

       System.out.println(i);

   }

   }


 

        synchronized实现原理

   

            monitorenter指令时会尝试获取相应对象的monitor,获取规则如下:

   

                如果monitor的进入数为0,则该线程可以进入monitor,并将monitor进入数设置为1,该线程即为monitor的拥有者。

                如果当前线程已经拥有该monitor,只是重新进入,则进入monitor的进入数加1,所以synchronized关键字实现的锁是可重入的锁。

                如果monitor已被其他线程拥有,则当前线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor。

   

            monitorexit:

   

                只有拥有相应对象的monitor的线程才能执行monitorexit指令。每执行一次该指令monitor进入数减1,当进入数为0时当前线程释放monitor,此时其他阻塞的线程将可以尝试获取该monitor。

   

        synchronized的内存语义

   

            1. 进入synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。

            2. 退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。


 

2、今天没学会什么

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

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

相关文章

双塔模型在召回和粗排的区别

答案参考&#xff1a;推荐系统中&#xff0c;双塔模型用于粗排和用于召回的区别有哪些? - 知乎 召回和粗排在不同阶段面临样本不一样&#xff0c;对双塔来说样本分布差异会使召回和粗排采取不一样的方式。召回打分空间是全部item空间&#xff0c;曝光只有很少一部分&#xff0…

【机器学习】集成学习---Bagging之随机森林(RF)

【机器学习】集成学习---Bagging之随机森林&#xff08;RF&#xff09; 一、引言1. 简要介绍集成学习的概念及其在机器学习领域的重要性。2. 引出随机森林作为Bagging算法的一个典型应用。 二、随机森林原理1. Bagging算法的基本思想2. 随机森林的构造3. 随机森林的工作机制 三…

ClickHouse 如何实现数据一致性

文章目录 ReplacingMegreTree 引擎数据一致性实现方式1.ReplacingMegreTree 引擎2.ReplacingMegreTree 引擎 手动合并3.ReplacingMegreTree 引擎 FINAL 查询4.ReplacingMegreTree 引擎 标记 GroupBy5.允许偏差 前言&#xff1a;在大数据中&#xff0c;基本上所有组件都要求…

Docker创建镜像之--------------基于Dockerfile创建

目录 一、在编写 Dockerfile 时&#xff0c;有严格的格式需要遵循 二、Dockerfile 操作常用的指令 2.1ENTRYPOINT和CMD共存的情形 2.2ENTRYPOINT和CMD的区别 2.3ADD 与COPY的区别 三、Dockerfile案例 3.1构建apache镜像 3.1.1 创建镜像目录方便管理 3.1.2创建编写dock…

深入浅出MySQL-03-【MySQL中的运算符】

文章目录 前言1.算术运算符2.比较运算符3.逻辑操作符4.位运算符5.运算符的优先级 前言 环境&#xff1a; Windows11MySQL-8.0.35 MySQL支持多种类型的运算符&#xff0c;可以用来连接表达式的项。运算符的类型主要包括 算术运算符、比较运算符、逻辑运算符 和 位运算符。 1…

在vue项目中使用TS

在vue项目中使用TS 1. 将vue项目注入ts 引入和使用 webpack的打包配置&#xff1a;vue-cli webnpack 编译时 entry 入口 设置 entry: {app: ./src/maikn.ts }2. resolve: extensions 添加 ts 用于处理尝试的数据尾缀列表 问&#xff1a; 如何在webpack新增处理类型文件&am…

函数递归与迭代

目录 1.递归 1.1递归的思想 1.2递归的限制条件 2.递归与迭代 1.递归 函数递归是什么&#xff1f; 递归是学习C语⾔函数绕不开的⼀个话题&#xff0c;那什么是递归呢? 递归其实是⼀种解决问题的⽅法&#xff0c;在C语⾔中&#xff0c;递归就是函数⾃⼰调⽤⾃⼰。 写⼀个史…

代码随想录算法训练营第五十三天| 1143.最长公共子序列 ,1035.不相交的线,53. 最大子序和 动态规划

题目与题解 1143.最长公共子序列 题目链接&#xff1a;1143.最长公共子序列 代码随想录题解&#xff1a;​​​​​​​1143.最长公共子序列 视频讲解&#xff1a;动态规划子序列问题经典题目 | LeetCode&#xff1a;1143.最长公共子序列_哔哩哔哩_bilibili 解题思路&#xff…

Linux Makefile编写之可执行程序

1 概述 编译工具有很多(make/cmake/BJam)。如果不考虑跨平台的话&#xff0c;还是make比较方便。使用make编译需要编写Makefile。本文编写Makefile来生成C/C可执行程序。 2 Makefile文件命名 Makefile文件首先是一个文本文件&#xff0c;Linux下默认有两种命名方式: Makefil…

DBSCAN算法学习

DBSCAN算法 文章目录 DBSCAN算法概述应用场景优缺点基于sklearn库的样例DBSCAN、分层聚类和K均值聚类比较 概述 DBSCAN算法是一种基于密度的聚类算法&#xff0c;能够自动识别不同的簇&#xff0c;并与噪声数据分开。以下是关于DBSCAN算法的重要知识点概述&#xff1a; 基本概…

vue3中如何父组件中使用弹框,子组件中关闭弹框

子组件: <template><el-dialogv-model"visible"title"Tips"width"500"><div class"left"></div><div class"right"></div><template #footer><div class"dialog-footer…

Learning to Upsample by Learning to Sample

摘要 论文&#xff1a;https://arxiv.org/pdf/2308.15085 我们提出了DySample&#xff0c;一个超轻量级且高效的动态上采样器。虽然最近的基于内核的动态上采样器&#xff0c;如CARAFE、FADE和SAPA&#xff0c;取得了令人印象深刻的性能提升&#xff0c;但它们引入了大量的计算…

前端实现文件下载的方法

一、简介 ​ 之前我分享过《前端实现图片下载的方法》&#xff0c;但那只是针对图片下载的方法。本博客分享的是对于文件的下载方法&#xff0c;包括图片文件和非图片文件的下载&#xff0c;例如png、doc、pdf、ppt等等。 ​ 当然&#xff0c;还是那个大前提&#xff1a;在任…

大模型对数字营销的驱动赋能

一、大模型驱动的营销数智化个信未来发展趋势 1.模型算法能力全面升级 大模型凭借智能化的用户洞察&#xff0c;个性化的需求预测、系统化的数据分析、效率化的营销决策以及实实化的全域检测支持&#xff0c;为营销行业更加准确地把握市场动态和消费者需求提供了强大支持。可以…

Spring Boot 如何实现缓存预热

Spring Boot 实现缓存预热 1、使用启动监听事件实现缓存预热。2、使用 PostConstruct 注解实现缓存预热。3、使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。4、通过实现 InitializingBean 接口&#xff0c;并重写 afterPropertiesSet 方法实现缓存预热。 1、使用…

数据结构和算法:贪心

贪心算法 贪心算法是一种常见的解决优化问题的算法&#xff0c;其基本思想是在问题的每个决策阶段&#xff0c;都选择当前看起来最优的选择&#xff0c;即贪心地做出局部最优的决策&#xff0c;以期获得全局最优解。 贪心算法和动态规划都常用于解决优化问题。它们之间存在一…

TCP/IP协议族中的TCP(二):解析其关键特性与机制

⭐小白苦学IT的博客主页⭐ ⭐初学者必看&#xff1a;Linux操作系统入门⭐ ⭐代码仓库&#xff1a;Linux代码仓库⭐ ❤关注我一起讨论和学习Linux系统 滑动窗口 在前面我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段.这样…

力扣HOT100 - 98. 验证二叉搜索树

解题思路&#xff1a; class Solution {public boolean isValidBST(TreeNode root) {return recur(root,Long.MIN_VALUE,Long.MAX_VALUE);}public boolean recur(TreeNode root,long lower,long upper){if(rootnull) return true;if(root.val<lower||root.val>upper) re…

HTTP Host 头攻击 原理以及修复方法

漏洞名称 &#xff1a;HTTP Host头攻击 漏洞描述&#xff1a; 一般通用web程序是如果想知道网站域名不是一件简单的事情&#xff0c;如果用一个固定的URI来作为域名会有各种麻烦。开发人员一般是依赖HTTP Host header&#xff08;比如在php里_SERVER["HTTP_HOST"] …

Ubuntu上的screenfetch

2024年4月28日&#xff0c;周日下午 这些文本是由一个叫做 “screenfetch” 的命令生成的&#xff0c;它会显示一些系统和用户信息&#xff0c;包括操作系统、内核版本、系统运行时间、安装的软件包数量、使用的Shell、分辨率、桌面环境、窗口管理器、主题、图标主题、字体、CP…