返回首页  设为首页  加入收藏  今天是:
网站首页电脑主板电脑cpu电脑内存电脑硬盘电脑显卡电脑电源显示器电脑配件电脑维修
相关文章
 全网爆赞万字图解工作面试必…
 难怪手机128G内存不够用原来…
 关于计算机除了冯·诺依曼这…
 聊一台“便携性极高”的性价…
 快速解决主板相关问题攻略!
 AM2强援 梅捷“NF570”主板报…
 男子阳后改良空调主板可实现…
 网络投资遭遇诈骗 警方千里抓…
 笔记本主板04XW3R什么意思
 价格会便宜吗?英伟达确认Ge…
 AMD、NV让你失望了!2023年显…
 传英伟达确定RTX 4070 Ti价格…
 英伟达确认“GeForce Beyond…
 英伟达官方确认 RTX 4070 Ti…
 搞定7800!九款最强显卡专用…
 你的电源线新品显卡电源选购…
 绝对发烧 Thermaltake 展示显…
 RTX 40系显卡价格多少钱 406…
 道高一尺 魔高一丈!显卡电源…
 移动硬盘遗失之后 朋友向我推…
 铁威马推出两款NAS支持20TB硬…
 团 onemodern智能手机移动硬…
 【手慢无】老机再战三年!1T…
 数读科创板IPO联芸科技:固态…
 华硕灵耀Pro16和联想ThinkBo…
 春节快到了去做这4个生意市场…
 深市上市公司公告(7月28日)
 大学生新电脑被高铁前座压坏…
 高效办公+智慧互联华为“书房…
 2022年2月14日显卡价格日报-…
 RTX 4090显卡价格是多少 能玩…
 2022年1月23日显卡价格日报-…
 2022年1月17日显卡价格日报-…
 RTX 4090显卡价格是多少 比3…
 KTC显示器评测:165Hz高刷新…
 显示器市场的 2022:疯狂降价…
 显示器市场的年终疯狂低至20…
 冠捷科技: 减碳从显示器开始…
 IDC:预计2023年中国PC显示器…
 CPU天梯图2019年1月最新版 一…
 CPU天梯图2019年3月最新版 三…
 电脑cpu处理器排名台式电脑c…
 台式电脑cpu排行榜(台式电脑…
 计算机台式硬件排名CPU天梯图…
 最新]内存常识
 内存容量4gb什么意思
 “LMI”是“Local Memory In…
 硬件原理图中的“英文缩写”…
 作为一个程序员内存的这些硬…
 B站一名电脑维修UP 主发现英…
专题栏目
网络
您现在的位置: 电脑评测网 >> 电脑内存 >> 正文
高级搜索
全网爆赞万字图解工作面试必备Java线程安全问题和解决方案
作者:佚名 文章来源:本站原创 点击数: 更新时间:2022/12/30 6:13:38 | 【字体:

  农村创业故事上一篇介绍了《Java多线程的作用》,使用场景和创建方式等基础,本篇主要介绍:

  文章涵盖广而全,对工作和面试都有很大帮助,值得收藏认真阅读,不错的话记得点赞,关注支持哦!

  一旦调用start方法,线程处于runnable状态【可运行状态】。也就是可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。(Java的规范说明没有将它作为一个单独状态。一个正在运行中的线程仍然处于可运行状态。)

  一旦一个线程开始运行,它不必始终保持运行。运行中的线程被中断,目的是为了让其他线程获得运行机会。线程调度的细节依赖于操作系统提供的服务。抢占式调度系统给每一个可运行线程一个时间片来执行任务。当时间片用完,操作系统剥夺该线程的运行权,并给另一个线程运行机会。当选择下一个线程时,操作系统考虑线程的优先级。

  现在所有的桌面以及服务器操作系统都是用抢占式调度。但是,像手机这样的小型设备可能使用协作式调度,在这样的设备中,一个线程只有调用yield方法,或者被阻塞或等待时,线程才失去控制权。

  在具有多个处理器的机器上,每一个处理器运行一个线程,可以有多个线程并行运行。当然,如果线程的数目多于处理器的数目,调度器依然采用时间片机制。

  记住,在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行(这就是为什么将这个状态称为可运行而不是运行)

  Java的多线程可以充分利用CPU资源提高计算速度和处理后台任务等,而且线程之间的运行机制是抢占式,或者说是随机的,这就会导致多个线程对共享数据操作时可能出现错误的结果,对于多线程并发时会使程序出现bug的代码称作线程不安全的代码,这就是线程安全问题

  比如公司研发了一款手机,提供两个售货渠道卖10部手机,每一个线程就是一个售货渠道,当然最多只能卖出10部不能超卖

  运行结果发现出现了0号手机,卖出了11部,明显是有问题的。你也可以试着运行,每次的运行结果不一样,而且出现这种BUG也是随机的,你可能运行十几二十次都不会出现这问题

  要明确一个前提是只有得到CPU的时间片线程才会被执行,而且CPU不保障一次将线程执行完,也就是说,CPU会在线程之间切换执行,上述例子出现超卖的原因也在这里

  当还有最后一部手机时,线经过while循环判断进入循环体,这时还没有对手机进行售卖,线的时间片用完,线程挂起,CPU开始执行线这时判断while循环条件,仍然成立,进入线部手机】,这时切换到线

  上次线已经通过了while循环的判断,所以继续执行,不会再次进行判断,但是stocks值已经被线,这时再输出就是【售卖渠道2卖出第0部手机】

  会对变量进行-1操作,--在变量之后,所以是后--,意思是如果变量参与了运算,则先完成运算再进行-1操作,比如上述例子与字符串进行相加运算,所以stocks变量会先于字符串完成拼接输出数据之后再对变量进行 -1 操作而且程序运行时需要交给CPU执行,系统在执行运算时会将代码转换为

  进行运算,在指令集方面,stocks--这样的一个自减操作会被分成三个指令操作:

  线程之间指令集无交叉,运行结果与预期相同,线从内存加载值,运算之后再将值存进内存,线,while判断不成立

  线程之间指令集存在交叉,结果可能存在问题,指令交叉计算后得知没有及时刷新进内存,导致另外的线程获取到的是旧值,就会存在少减情况

  根据上边的几种情况分析,发现线程运行时没有出现指令交叉结果是预期的,如果出现指令交叉就会存在库存少减现象,是因为自减操作不是原子的是可以再分割的,线程之间独立,线程内计算的值并没有直接刷新进内存,导致别的线程并不会得到最新的数据,多线程并发执行时很可能出现指令交叉,导致线程安全问题,出现错误结果。

  】的现象,这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作是非“原子化”的,可能前一个线程对数据的操作还没有结束,后一个线程又开始对同样的数据开始进行操作,这就可能会造成数据结果的变化未知。为了解决由于【抢占式执行】导致的线程安全问题,我们可以对共享数据进行加锁,可以理解为给多线程操作的共享数据设置一个操作权限,谁拿到这个锁,谁就有权利操作共享数据,当一个线程拿到共享数据的锁后,就会把共享数据锁起来,其他线程如果也要操作这个共享数据,需要等待已经获取到锁的线程执行完之后释放锁,其他拿个线程得到这个锁,谁就可以操作共享数据。

  举个例子:有一家饭店的包间非常不错,很多人都想在包间中就餐。当包间被顾客预定之后就相当于被上了锁,其他顾客必须等待上一个顾客享用完服务之后才可再预定使用,预定到的就会再次对包间上锁,其他顾客无法享用这个包间。这样就不会乱糟糟的了是吧,不然就跟没有秩序一样,谁都可以进包间里边就会发生冲突。这里的顾客就是一个一个的线程,这里的包间就是共享数据,预定包间成功就相当于加的锁

  当你使用完之后,释放锁,其他线程竞争锁,当一个线程抢到锁之后,就会进入套房享用服务

  效果,即同一时刻只能有一个线程操作共享数据,某个线程执行到 synchronized 中时, 其他线程如果也执行这块代码,就会阻塞等待。线程进入 synchronized 修饰的代码块, 相当于加锁,退出 synchronized 修饰的代码块, 相当于解锁

  使用synchronized关键字修饰方法,这样会使方法所在的对象加上一把锁

  上边的代码可以解决线程安全问题,但是因为while条件中直接判断的共享资源,所以将while直接锁进嘞小房间,所以所有的手机都会被同一个线程售出,比如:线获取到锁资源后上锁,进入while循环,沉迷其中不可自拔,一口气消费完才释放锁。我们可以通过以下代码优化,实现线程交替运行:

  定义 flag 变量标记是否还有库存,while循环判断库存标记,这样可以保障当线判断while之后挂起还没有调用售卖方法时仍然可能丢失CPU执行权,切换到其他线程执行。

  在运行结果截图中,发现写了一个sleep方法,这是为了让线程进入超时等待可以释放CPU执行权,来达到切换线程的目的,实际开发中是不会使用sleep方法的,所以上边贴出的代码中并没有sleep方法调用

  为可看出效果,也可以将库存调为10万台,有充分的资源支撑线程切换,可以看出下图同样线和线之间切换,并且没有出现超卖现象

  使用synchronized关键字对代码段进行加锁,但是需要显式指定加锁的对象。

  使用synchronized关键字修饰静态方法,相当于对当前类的类对象进行加锁

  常见的用法差不多就是这些,对于线程加锁(线程拿锁),如果两个线程同时拿一个对象的锁,就会产生锁竞争,两个线程同时拿两个不同对象的锁不会产生锁竞争。 对于synchronized这个关键字,它的英文意思是

  ,但是同步在计算机中是存在多种意思的,比如在多线程中,这里同步的意思是“互斥”;而在IO或网络编程中同步则是另一个意思

  ,即一个线程已经获得了某个锁,当这个线程要再次获取这个锁时,依然可以获取成功,不会发生死锁的情况。synchronized就是一个可重入锁。可重入的条件

  使用本地数据(工作内存),或者通过制作全局数据的本地拷贝来保护全局数据。

  一般而言,可重入的函数一定是线程安全的,反之则不一定成立。在不加锁的前提下,如果一个函数用到了全局或静态变量,那么它不是线程安全的,也不是可重入的。如果我们加以改进,对全局变量的访问加锁,此时它是线程安全的但不是可重入的,因为通常的加锁方式是针对不同线程的访问(如Java的synchronized),当同一个线程多次访问就会出现问题。只有当函数满足可重入的四条条件时,才是可重入的

  从设计上讲,当一个线程请求一个由其他线程持有的对象锁时,该线程会阻塞。当线程请求自己持有的对象锁时,如果该线程是重入锁,请求就会成功,否则阻塞。

  我们回来看synchronized,synchronized拥有强制原子性的内部锁机制,是一个可重入锁。因此,在一个线程使用synchronized方法时调用该对象另一个synchronized方法,即一个线程得到一个对象锁后再次请求该对象锁,是

  。在Java内部,同一个线程调用自己类中其他synchronized方法/块时不会阻碍该线程的执行,同一个线程对同一个对象锁是可重入的,同一个线程可以获取同一把锁多次,也就是可以多次重入。原因是Java中线程获得对象锁的操作是以线程为单位的,而不是以调用为单位的。

  每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,jvm会记下持有锁的线。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。

  当下成1获取到锁对象之后就会将共享资源锁起来【lock】,当线处理完之后释放锁【unlock】,其他线程来竞争这把锁,谁得到锁谁就将资源锁住【lock】,依次释放和获得锁,没有获取到锁的线程就会进入阻塞状态

  加锁后线程就是串行执行,与单线程其实没有很大的区别,那多线程是不是没有用了呢?但是对方法加锁后,线程运行该方法才会加锁,运行完该方法就会自动解锁,况且大部分操作并发执行是不会造成线程安全的,只有少部分的修改操作才会有可能导致线程安全问题,因此整体上多线程运行效率还是比单线程高得多。

  从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用

  接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问。每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象ReentrantLock类实现了Lock,ta拥有与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是

  使用 ReentrantLock 的时候,建议把 Lock 和 方法体 放在 try{} 代码块中,然后释放锁 unlock() 放在 finally{} 代码块中保证锁释放成功~,如果线程发生异常意外终止,锁没有释放成功,别的线程也获取不到锁,就会出现死锁,也就是谁都拿不到锁,谁都运行不了程序

  synchronized是隐式的加锁和解锁,以获取锁的线程执行完同步代码,释放锁,或线程执行发生异常,jvm会让线程释放锁,lock是显示的加锁和解锁,在finally中必须释放锁,不然容易造成线程死锁

  synchronized可以作用在方法和代码块上,而lock只能作用在代码块上

  synchronized是阻塞式加锁,假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待,而lock中获取锁分情况而定,Lock有多个锁获取的方式,可以通过trylock方法尝试获取锁,线程可以不用一直等待,支持非阻塞式加锁

  synchronized 是可重入 不可中断 非公平锁,Lock有多种实现,可以是可重入 可判断 可公平锁(两者皆可

  tryLock就是尝试获取锁,如果所被别的线程获取,则直接放弃获取,不阻塞,好比追一个小姐姐,人家有对象了,直接放弃,而lock则是等着【分手接盘】

  ,则是锁被别的线程拿到,会等待指定时间,如果还没获取到就放弃,好比给小姐姐一段时间分手,如果没分就拉到,分了就接盘

  ,默认为非公平锁。面试时Java中的锁分类也是高频问点!在下边也为大家介绍到:

  非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

  对于ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

  对于synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁

  可重入锁又名递归锁,是指同一个线程如果获取到锁对象,在线程内的其他代码块中部会自动获取锁。

  对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是ReentrantLock重新进入锁。

  对于synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁

  private void method(){ // 获取this对象锁 synchronized (this) { // 再次获取统一对象的锁,仍然可以 synchronized (this) { } } }

  对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。

  读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

  独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

  上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

  悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。

  乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

  从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

  乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新

  分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

  我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

  当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

  但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

  分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作

  这三种锁是指锁的状态,并且是针对synchronized。在Java 5通过引入锁升级的机制来实现高效synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

  偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

  轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

  重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低

  在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

  Java是用字节码指令来控制程序(这里不包括热点代码编译成机器码)。在字节指令中,存在有synchronized所包含的代码块,那么会形成2段流程的执行

  如上就是这段代码段字节码指令,我们可以清晰段看到,其实synchronized映射成字节码指令就是增加来两个指令:

  和monitorexit。当一条线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁那么锁计数+1(因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况),如果没有获得锁,那么阻塞。当它遇到monitorexit的时候,锁计数器-1,当计数器为0,那么就释放锁。有的朋友看到这里就疑惑了,为什么有

  呀?马上回答这个问题:synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是发送异常,虚拟机释放。图中第二个monitorexit就是发生异常时执行的流程。而且,从图中我们也可以看到在第18行,有一个goto指令,也就是说如果正常运行结束会跳转到26行执行。

  Lock实现和synchronized不一样,后者是一种悲观锁,它胆子很小,它很怕有人和它抢吃的,所以它每次吃东西前都把自己关起来。而Lock呢底层其实是CAS乐观锁的体现,它无所谓,别人抢了它吃的,它重新去拿吃的就好啦,所以它很乐观。具体底层怎么实现,如果面试问起,你就说底层主要靠volatile和CAS操作实现的。

  Java线程其实是映射在内核之上,线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大的提升了性能。 而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。它可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起

  】锁消除就是把不必要的同步在编译阶段进行移除,惊讶!我自己写的代码我会不知道这里要不要加锁?需要你教我做事?我加了锁就是表示这边会有同步呀? 并不是这样,这里所说的锁消除并不一定指代是你写的代码的锁消除,而是根据

  ,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁,我打一个比方: 在jdk1.5以前,我们的String字符串拼接操作其实底层是StringBuffer实现,而在jdk1.5之后,是用StringBuilder来拼接。我们考虑前面的情况,比如如下代码:

  StringBuffer是一个线程安全的类,也就是说两个append方法都会同步,通过指针逃逸分析(就是变量不会外泄),我们发现在这段代码并不存在线程安全问题,这个时候就会把这个同步锁消除

  。Hotspot 确实进行了锁粗化优化,可以有效合并几个相邻同步块,从而降低锁开销。能够把下面的代码

  理论上,没有什么能阻止我们这样做,甚至可以把这种优化看作只针对锁的优化,像

  一样。然而,缺点是可能把锁优化后变得过粗,线程在执行循环时会占据所有的锁

  轻量级锁和偏向锁在上边锁分类中已经解释,不再复述,JDK将 synchronized 升级成了这两种特性的锁

  这里对Java多线程的运行机制,线程安全问题产生的原因和解决方案,锁分类,并对 synchronized 和 Lock的底层原理进行分析。多线程是一门比较深的学问,不同的场景使用方法都不同,但是本质几乎一样,如果您对本文有什么疑问或者问题欢迎在评论区指出。

  Java的多线程仅仅是开始远没有结束,比如多线锁问题,JUC中的原子类,volatile关键字,ThreadLocal,分布式锁,线程通信,JDK中各个线程安全类如何实现线程安全的等等都会陆续更新出来,欢迎持续关注!

电脑内存录入:admin    责任编辑:admin 
  • 上一个电脑内存:

  • 下一个电脑内存: 没有了
  •  
     栏目文章
    普通电脑内存 全网爆赞万字图解工作面试必备Java线程安全问… (12-30)
    普通电脑内存 难怪手机128G内存不够用原来是微信这个功能没… (12-30)
    普通电脑内存 关于计算机除了冯·诺依曼这些你也得知道 (12-30)
    普通电脑内存 聊一台“便携性极高”的性价比电脑 (12-30)
    普通电脑内存 最新]内存常识 (12-29)
    普通电脑内存 内存容量4gb什么意思 (12-29)
    普通电脑内存 “LMI”是“Local Memory Interface”的英文缩… (12-29)
    普通电脑内存 硬件原理图中的“英文缩写”大全 (12-29)
    普通电脑内存 作为一个程序员内存的这些硬核知识你必须懂! (12-29)
    普通电脑内存 【世界时快讯】怎么手动清理内存_手动清理内存 (12-29)
    普通电脑内存 电脑内存条到底要多大容量才合适很多人都误解… (12-29)
    普通电脑内存 Steam Deck能当电脑用吗 可以插SD内存卡吗? (12-29)
    普通电脑内存 听说近来电脑内存升价厉害不知道什么时候降下… (12-29)
    普通电脑内存 你在DIY电脑时会怎么选择内存条 (12-29)
    普通电脑内存 购新机弄懂这几个技巧不吃亏!北京电信岁末双… (12-29)
    普通电脑内存 内存DDR和KVR是什么意思有什么区别 (12-29)
    普通电脑内存 【手慢无】充满仅需十几分钟!华为鸿蒙5G新机… (12-29)
    普通电脑内存 物理内存和虚拟内存是什么意思各有什么作用_教… (12-29)
    普通电脑内存 2023重庆知了喜剧脱口秀小剧场元旦跨年专场演… (12-29)
    普通电脑内存 创新乏力的苹果凭什么还能大卖? (12-28)