解密Java中神奇的Synchronized关键字

文章目录

      • 🎉 定义
      • 🎉 JDK6以前
      • 🎉 偏向锁和轻量级锁
        • 📝 偏向锁
        • 📝 轻量级锁
        • 📝 自旋锁
        • 📝 重量级锁
          • 🔥 1. 加锁
          • 🔥 2. 等待
          • 🔥 3. 撤销
      • 🎉 锁优化
        • 📝 锁消除
        • 📝 锁粗化
        • 📝 自适应自旋
      • 🎉 synchronized关键字的用法和注意事项
        • 📝 修饰方法
        • 📝 修饰代码块
        • 📝 修饰静态方法
        • 📝 修饰类

📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。🌎跑过十五公里、徒步爬过衡山、🔥有过三个月减肥20斤的经历、是个喜欢躺平的狠人。

📘拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、Spring MVC、SpringCould、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RockerMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。🎥有从0到1的高并发项目经验,利用弹性伸缩、负载均衡、报警任务、自启动脚本,最高压测过200台机器,有着丰富的项目调优经验。

📙经过多年在CSDN创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续在明年出版。这些书籍包括了基础篇、进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!

以梦为马,不负韶华

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

  • 💂 博客主页: 我是廖志伟
  • 👉开源项目:java_wxid
  • 🌥 哔哩哔哩:我是廖志伟
  • 🎏个人社区:幕后大佬
  • 🔖个人微信号SeniorRD

💡在这个美好的时刻,本人不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。

CSDN

解密Java中神奇的Synchronized关键字

在Java中,当多个线程同时访问同一块代码,会产生竞态条件,可能会导致数据不一致或其他问题。为了解决这个问题,Java提供了synchronized关键字,它能够保证同一时刻被synchronized修饰的代码最多只有1个线程执行。本文将从synchronized的定义、JDK6以前的实现方式、偏向锁和轻量级锁、锁优化、synchronized关键字的用法和注意事项等方面详细讲解。

🎉 定义

在Java中,synchronized关键字是一种同步锁,在多线程编程中,用于解决多个线程同时访问同一个资源的问题。当一个线程持有锁时,其他线程将会被阻塞,直到当前线程释放锁为止。synchronized可以加在方法上或对象上,作用的对象是非静态的,则取得的锁是对象锁;作用的对象是静态方法或类,则取到的锁是类锁,这个类所有的对象用的是同一把锁。

🎉 JDK6以前

在JDK6以前,synchronized加锁是通过对象内部的监视器锁来实现的,监视器锁本质上又是依赖于底层的操作系统的Mutex Lock来实现的,因此在高并发情况下,synchronized的性能就会变得非常低下。

当多个线程争夺同一个锁时,会发生线程阻塞和唤醒的操作,这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要比较长的时间,导致程序执行效率低下。

🎉 偏向锁和轻量级锁

为了提高synchronized的效率,JDK6引入了偏向锁和轻量级锁。

📝 偏向锁

在Java中,同步操作需要获取锁来保证多个线程访问共享资源的安全性。而在锁的获取过程中,JVM添加了一种优化方式——偏向锁。当一个线程访问同步块时,如果这个同步块没有被其他线程占用,那么JVM会把这个同步块的锁标记为偏向锁,并把当前线程ID记录在MarkWord中。这样,下次同一线程访问同步块时,就不需要再次获取锁,直接进入同步块即可。

但是,当有其他线程来竞争锁的时候,偏向锁需要进行撤销,转而使用轻量级锁或者重量级锁来保证同步操作的安全性。这里给出一个对象从无锁到偏向锁转化的过程:

第一步,检测MarkWord是否为可偏向状态,即锁标识位是01,如果是,则说明当前对象可被偏向,可以执行同步代码块。

第二步,如果需要访问同步块的线程ID与MarkWord中记录的线程ID相同,则不需要进行竞争,直接执行同步代码块即可。

第三步,如果需要访问同步块的线程ID与MarkWord中记录的线程ID不同,说明该对象已经被其他线程占用,需要进行竞争。此时,JVM会进行CAS操作,如果操作成功,则将线程ID替换为当前线程ID,并执行同步代码块。

第四步,如果CAS操作失败,则需要进行偏向锁的撤销。这个过程可以有两种情况触发:一是对象头的Epoch字段计数到一定次数,二是多个线程尝试竞争该对象的锁,都失败了。

第五步,完成偏向锁的撤销后,持有偏向锁的线程不会被挂起,继续执行同步代码块。如果获取锁失败,则视情况进行自旋或者进行阻塞等待,进一步升级为轻量级锁或重量级锁。

需要注意的是,在偏向锁撤销的过程中,需要清除那些曾经持有该偏向锁对象的线程的锁记录。这是因为在偏向锁状态下,持有锁的线程会在对象头中记录一个标记位和持有该锁的线程ID。而在撤销偏向锁的过程中,需要清除这些锁记录,因为它们已经不再持有该锁,以便其他线程可以重新争夺锁的所有权。并且,偏向锁撤销的过程不一定会挂起所有持有偏向锁的线程,只有在线程竞争锁时才会挂起线程。

在偏向锁撤销过程中,JVM会启动偏向锁撤销线程来遍历所有持有该偏向锁对象的线程栈,清除它们的锁记录。而在多线程编程中,当多个线程对一个内存位置进行读取和修改时,可能会出现一种情况——ABA问题。为了解决这个问题,JVM 在对象的内存布局中添加了一个Epoch字段,来判断一个线程是否因为ABA问题导致的线程变化。这个Epoch字段并不直接关系到偏向锁撤销的过程,但是有助于判断锁的状态是否发生了变化。

在实际的应用中,偏向锁的优化方式能够显著提高同步代码块的性能,但它并不适用于所有场景。在多线程应用程序中,如果存在大量的锁竞争,那么偏向锁的优化效果会下降,甚至被轻量级锁或重量级锁取代。因此,在使用偏向锁的时候,需要根据具体情况进行考虑和使用。同时,了解偏向锁的撤销过程,有助于我们更好地理解同步机制的底层实现,更好地进行多线程编程。

📝 轻量级锁

轻量级锁升级过程是为了实现对象的互斥访问,首先在当前线程的栈帧中创建一个锁记录用于存储锁对象的MarkWord的拷贝。该拷贝无锁状态对象头中的MarkWord,用于在申请对象锁时作为CAS的比较条件。同时也能通过这个比较判定是否在持有锁的过程中,这个锁被其他线程申请过,如果有,在释放锁的时候要唤醒被挂起的线程。轻量级锁的MarkWord如果存有hashCode,解锁后也需要恢复。拷贝成功后,虚拟机使用CAS操作把对象中对象头的MarkWord替换为指向锁记录的指针,再把锁记录空间里的owner指针指向加锁的对象。如果这个更新操作成功,那么当前线程就拥有了该对象的锁,并且对象MarkWord的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。

如果更新操作失败,虚拟机会检查对象的MarkWord中的Lock Word是否指向当前线程的栈帧。如果是,则当前线程已经拥有了这个对象的锁,可以直接进入同步块继续执行。如果不是,则说明多个线程竞争锁,要进入自旋。若自旋结束时依然未获得锁,轻量级锁就要升级为重量级锁,锁标志的状态值变为“10”,对象MarkWord中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后续等待锁的线程也要进入阻塞状态。

当锁升级为轻量级锁后,如果依然有新线程过来竞争锁,首先新线程会自旋尝试获取锁,尝试到一定次数(默认10次)依然没有拿到,锁就会升级成重量级锁。一般来说,同步代码块内的代码应该很快就执行结束,这时线程B自旋一段时间是很容易拿到锁的。但如果不巧,没能拿到锁,自旋就会成为死循环,并且耗费CPU。因此,虚拟机会直接将锁升级为重量级锁,不再进行自旋。这样就不需要了线程一直自旋,性能会得到很大的提升。

📝 自旋锁

自旋锁并不是一种锁状态,而是一种智能的线程同步策略。它可以用于保护临界区的并发访问,避免多个线程同时进入临界区导致的数据不一致问题。自旋锁的核心思想是,在等待锁的过程中,不会主动阻塞线程,而是继续执行当前线程内的代码,同时不断自旋等待锁的释放,以减少线程的阻塞和唤醒,提高并发性能。

当一个线程尝试获取某个锁时,如果发现该锁已经被其他线程占用,则该线程不会被立即挂起或者睡眠,而是开始自旋等待锁的释放。自旋等待期间,该线程会一直占用CPU处理器资源,循环检测锁是否被释放。自旋等待不能替代阻塞,因为如果自旋时间过长,会占用过多CPU资源,反而降低性能。

自旋锁适用于维护临界区很小的情况。临界区很小表示锁占用的时间很短,如果持有锁的线程很快就能释放锁,那么自旋的效率就会非常高。但是自旋的次数必须要有一个限度,如果自旋超过了定义的次数仍然没有获取到锁,就应该被挂起。然而这个限度不能固定,因为程序锁的状况是不可预估的,所以JDK1.6引入了自适应的自旋锁,可以根据程序运行的情况动态调整自旋的次数。比如如果线程自旋成功了,那么下次自旋的次数会更多,反之则会更少,从而避免了自旋等待过程中浪费处理器资源的情况。

要开启自旋锁,可以使用JDK1.6之后提供的参数–XX:+UseSpinning,如果需要修改自旋次数,可以使用–XX:PreBlockSpin参数来指定,其中默认值为10次。使用自旋锁可以在一定程度上提高多线程程序的性能,但也需要注意合理设置自旋次数和使用范围,以免造成过多的CPU资源占用和线程的饥饿等问题。

📝 重量级锁

重量级锁是Java中的一种锁,是通过对象内部的监视器锁(Monitor)来实现的。监视器锁本质上又是依赖于底层的操作系统的MutexLock来实现的。由于操作系统实现线程之间的切换需要从用户态转换到核心态,状态之间的转换需要比较长的时间,因此依赖于操作系统MutexLock所实现的锁我们称之为“重量级锁”。

当一个线程在等待锁时,会不停的进行自旋,其中自旋的线程数达到CPU核数一半之后,就会升级为重量级锁。在升级为重量级锁之后,锁标志会被置为10,同时将MarkWord中的指针指向重量级的Monitor,这将阻塞所有没有获取到锁的线程。

重量级锁的加锁-等待-撤销流程分为三个步骤。

🔥 1. 加锁

当一个线程请求锁时,首先会查看锁标志是否为0,如果为0,则表示锁没有被占用,此时该线程会将锁标志置为1,并且获取到锁。如果锁标志不为0,则表示锁已经被占用,此时该线程会进入自旋状态。

🔥 2. 等待

在自旋状态下,如果锁一直没有被释放,那么自旋的线程数量会不断增加。当自旋的线程数量达到CPU核数的1/2时,就会升级为重量级锁。在升级为重量级锁之后,会将锁标志置为10,同时将MarkWord中的指针指向重量级的Monitor,这将阻塞所有没有获取到锁的线程。

🔥 3. 撤销

当一个线程释放锁时,会将锁标志置为0。此时正在等待的线程会被唤醒并争夺锁,曾经获得过锁的线程,在被唤醒之后会优先得到锁。如果一个线程在等待锁的过程中调用了wait()方法,则该线程会被加入到等待队列中,并通过wait_set等待被唤醒。如果一个线程在等待锁的过程中调用了notify()方法,则该线程会将等待队列中的第一个线程唤醒,等待队列中被唤醒的线程会被加入到同步队列中,并通过park()方法等待获取锁。

当重量级锁撤销之后,系统会将其转变为无锁状态。撤销锁之后会清除创建的Monitor对象,并修改markword,这个过程需要一段时间。Monitor对象是通过GC来清除的。当GC清除掉Monitor对象之后,锁就被撤销为无锁状态。

重量级锁适用于多线程互斥访问同一资源的情况。由于重量级锁性能较低,因此只在必要时才应该使用。在Java中,Synchronized关键字就是一种重量级锁。在使用Synchronized关键字时,应尽量减少锁的持有时间,这样可以提高程序的并发性能。

🎉 锁优化

针对于synchronized的性能问题和在某些情况下可能导致死锁的情况,Java提供了以下的锁优化:

📝 锁消除

在编译器层面消除不必要的加锁操作,将锁的范围缩小到最小,这样可以减少锁竞争的概率,提高程序的执行效率。

📝 锁粗化

在进行一系列操作时,将多个连续的加锁操作放在一个代码块中,这样可以减少加锁和解锁的开销,提高程序的执行效率。

📝 自适应自旋

当线程尝试获取同步锁失败后,它并不会立即进入阻塞状态,而是再次尝试获取同步锁,如果一段时间内失败的次数越多,就会进入阻塞状态。这个时间段就是自旋时间,是由操作系统动态调整的。

🎉 synchronized关键字的用法和注意事项

synchronized关键字的用法有以下几种:

📝 修饰方法

这种方式是修饰整个方法,即使方法中没有同步代码块,也会锁定这个方法,这种方式适用于整个方法需要同步的情况。

public synchronized void method() {// 同步代码块
}
📝 修饰代码块

这种方式是将同步代码块包在synchronized括号内,只有在执行到synchronized代码块时才会锁定,这种方式适用于只需要同步执行部分代码的情况。

public void method() {synchronized (this) {// 同步代码块}
}
📝 修饰静态方法

和修饰方法类似,这种方式是锁定整个静态方法,适用于整个静态方法需要同步的情况。

public synchronized static void method() {// 同步代码块
}
📝 修饰类

这种方式是锁定整个类,即使不同实例中的线程也会被锁定,适用于整个类需要同步的情况。

public void method() {synchronized (ClassName.class) {// 同步代码块}
}

需要注意的是,只有在多个线程访问同一块资源时,才需要使用synchronized关键字。如果同步块内的代码很少,那么锁的代价就会超过同步块内的代码的执行代价,从而导致程序执行效率变低。同时,在使用synchronized关键字时,需要考虑死锁问题,即多个线程无限制地等待对方释放锁的情况。因此,在编写代码时,需要特别注意同步块的范围和锁的粒度。

CSDN

🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

  • 💂 博客主页: 我是廖志伟
  • 👉开源项目:java_wxid
  • 🌥 哔哩哔哩:我是廖志伟
  • 🎏个人社区:幕后大佬
  • 🔖个人微信号SeniorRD

📥博主的人生感悟和目标

探寻内心世界,博主分享人生感悟与未来目标

  • 🍋程序开发这条路不能停,停下来容易被淘汰掉,吃不了自律的苦,就要受平庸的罪,持续的能力才能带来持续的自信。我本身是一个很普通程序员,放在人堆里,除了与生俱来的盛世美颜,就剩180的大高个了,就是我这样的一个人,默默写博文也有好多年了。
  • 📺有句老话说的好,牛逼之前都是傻逼式的坚持,希望自己可以通过大量的作品、时间的积累、个人魅力、运气、时机,可以打造属于自己的技术影响力。
  • 💥内心起伏不定,我时而激动,时而沉思。我希望自己能成为一个综合性人才,具备技术、业务和管理方面的精湛技能。我想成为产品架构路线的总设计师,团队的指挥者,技术团队的中流砥柱,企业战略和资本规划的实战专家。
  • 🎉这个目标的实现需要不懈的努力和持续的成长,但我必须努力追求。因为我知道,只有成为这样的人才,我才能在职业生涯中不断前进并为企业的发展带来真正的价值。在这个不断变化的时代,我必须随时准备好迎接挑战,不断学习和探索新的领域,才能不断地向前推进。我坚信,只要我不断努力,我一定会达到自己的目标。

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

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

相关文章

红队打靶:Misdirection打靶思路详解(vulnhub)

目录 写在开头 第一步:主机发现与端口扫描 第二步:Web渗透(80端口,战术放弃) 第三步:Web渗透(8080端口) 第四步:sudo bash提权 第五步:/etc/passwd利…

一文搞懂UART通信协议

目录 1、UART简介 2、UART特性 3、UART协议帧 3.1、起始位 3.2、数据位 3.3、奇偶校验位 3.4、停止位 4、UART通信步骤 1、UART简介 UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种双向、串行、异步的通信…

【c++Leetcode】141. Linked List Cycle

问题入口 思想:Floyds Tortoise and Hare 这个算法简单来说就是设置一个慢指针(一次移动一个位置)和一个快指针(一次移动两个位置)。在遍历过程中,如果慢指针和快指针都指向同一个元素,证明环…

spacy.load(“en_core_web_trf“)报错TypeError: issubclass() arg 1 must be a class

使用spacy时遇到的问题 写在最前面: 安装spacy和en_core_web_trf时需要保证二者版本一致 安装及查看对应spacy版本 安装 pip install spacy查看版本 import spacy spacy.__version__安装en_core_web_trf 直接安装(如果可以的话) pytho…

Flutter视图原理之StatefulWidget,InheritedWidget

目录 StatefulElement1. 构造函数2. build3. _firstBuild3. didChangeDependencies4. setState InheritedElement1. Element类2. _updateInheritance3. InheritedWidget数据向下传递3.1 dependOnInheritedWidgetOfExactType 4. InheritedWidget的状态绑定4.1. ProxyElement 在f…

.net6部署到linux上(CentOS Linux 7)

目录 一、先在linux上配置.net环境 添加 Microsoft 包存储库 安装 SDK 安装运行时 检查 SDK 版本可使用终端查看当前安装的 .NET SDK 版本。 打开终端并运行以下命令。 二、创建.net6 mvc项目 并发布 创建项目 修改默认端口 打包发布到文件夹 运行打包项目查看项目是否…

macOS telnet替代方式

前言 经过使用Linux,常常用Linux的telnet查看端口畅通,是否有防火墙,但是在mac上已经没有这个命令了,那么怎么使用这个命令或者有没有其他替代呢,win和linux是否可以使用相同的替代。macOS可以原生用nc命令替代&#…

C算法:使用选择排序实现从(大到小/从小到大)排序数组,且元素交换不可使用第三变量。

需求&#xff1a; 使用选择排序实现从(大到小/从小到大)排序&#xff0c;且元素交换不可使用第三变量 (异或交换法) 代码实现&#xff1a; #include <stdio.h> void maopao(int* array,int len,int(*swap)(int a,int b)) {int i,j;for(i0;i<len-1;i){for(ji1;j<…

【数据结构与算法】two X 树的遍历以及功能实现

前言&#xff1a; 前面我们已经提到过树、二叉树的概念及结构、堆排序、Top-k问题等的知识点&#xff0c;这篇文章我们来详解一下二叉树的链式结构等问题。 &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&#x1f4a5; ✨✨专栏:htt…

【智能家居】

面向Apple developer学习&#xff1a;AirPlay | Apple Developer Documentation Airplay AirPlay允许人们将媒体内容从iOS、ipad、macOS和tvOS设备无线传输到支持AirPlay的Apple TV、HomePod以及电视和扬声器上。 网页链接的最佳实践 首选系统提供的媒体播放器。内置的媒体播…

SpringCloud和Kubernetes的区别

又见小道仙&#xff1a; https://blog.csdn.net/Tomwildboar/article/details/129531315 对于SpringCloud在实际项目中并未使用过&#xff0c;只是自学过SpringCloud和SpringCloud Alibaba&#xff0c;也基于学习搭建过demo。 对于Kubernetes&#xff0c;目前这家公司就是使用…

Web APIs——事件监听以及案例

1、事件监听 什么是事件&#xff1f; 事件是在编程时系统内发生的动作或者发生的事情 比如用户在网页上单击一个按钮 什么是事件监听&#xff1f; 就是让程序检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应&#xff0c;也称为绑定事…

【RocketMQ集群】Linux搭建RocketMQ双主双从集群

在当今大数据时代&#xff0c;消息队列系统成为了构建高可用、可扩展和可靠的分布式应用的重要组件之一。而Apache RocketMQ作为一款开源的分布式消息中间件&#xff0c;以其高吞吐量、低延迟和可靠性而备受青睐。为了满足大规模应用的需求&#xff0c;搭建RocketMQ集群是一种常…

基于springboot实现基于Java的超市进销存系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现基于Java的超市进销存系统演示 摘要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;超市进销存系统也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#x…

Weights and Biases使用教程

Weights and Biases使用教程 安装和初始化实验跟踪跟踪指标跟踪超参数可视化模型检查日志 数据和模型版本控制使用Sweeps进行超参数调优数据可视化report Weights and Biases已经成为AI社区中最受欢迎的库之一。该团队在创建了一个平台&#xff0c;使机器深度学习学习工程师能够…

云安全—docker原理

0x00 前言 因为要学习docker相关的检测技术&#xff0c;所以需要对docker的原理进行基本的原因&#xff0c;不求彻底弄懂&#xff0c;但求懂点皮毛&#xff0c;如有不妥之处&#xff0c;还请斧正。 0x01 docker概述 docker起源 docker公司是在旧金山&#xff0c;由法裔美籍…

Java操作Elasticsearch(新增数据)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

广告掘金全自动挂机项目,单设备30+【软件脚本+技术教程】

广告掘金项目是一种越来越受欢迎的赚钱方式&#xff0c;它通过观看广告视频来获取收益。然而&#xff0c;手动观看每个广告视频可能会耗费大量时间和精力。为了简化操作并提升效率&#xff0c;我们可以利用全自动挂机脚本来完成这一任务。接下来&#xff0c;将为您介绍如何使用…

PLC单按钮启停算法汇总

单按钮启停在三菱PLC里可以通过简单的取反指令"ALT"实现,西门子PLC如何实现ALT指令,请参考下面文章链接,这篇博客我们汇总常用的单按钮启停实现方法,希望大家读了本篇博客后有所收获。 博途ALT指令 博途S7-1200/1500PLC 取反指令(ALT)-CSDN博客SMART PLC的ALT指…

安卓 实现60s倒计时的CountDownTimer(小坑)

安卓 实现60s倒计时的CountDownTimer&#xff08;小坑&#xff09; 前言一、CountDownTimer 是什么&#xff1f;二、代码示例1.使用2.小坑的点误差及时取消 总结 前言 前段时间写倒计时没有用线程&#xff0c;想换一种实现方式结果踩了个小坑&#xff0c;特此记录。 一、Count…