多线程概述

11 篇文章 0 订阅
7 篇文章 0 订阅
多线程概述

由于CPU核心越来越多,现代编程中总少不了多线程,而多线程编程在java中是比较容易的,这也算是java的一大优势。


多线程编程,总避不开一个话题,那就是线程安全。那么,如何保证线程安全呢?可能很多人第一时间想到的就是锁,是的,锁是一个很好的解决方案,那么有没有更好的方案呢?答案也是有的。


那么,不用锁如何做到线程安全呢?


首先我们需要了解,为什么会有线程安全问题,也就是为什么线程很多时候不加锁是不安全的。线程安全问题通常是由内存共享导致的,多个线程同时操作同一块内存,这样由于两个线程同时使用一块内存就会导致状态不一致,进而导致线程安全问题,也就是解决线程安全问题实际上是解决线程间共享内存问题。


那么,如果不共享内存呢?如果多个线程间不共享内存,而是各自使用各自的栈内存,这样就不会导致状态不一致,也就不存在线程安全问题了,所以这里可以看出,如果能从设计上避免共享内存,那么多线程程序即使不加任何锁也是安全的,此时性能也是最高的,所以优化时可以优先考虑是否可以从业务、算法上避免多线程间的内存共享。


当然,在实际场景中不可避免的会出现多个线程间共享内存的现象,那么当出现共享内存的现象时,如何提升多线程程序的性能呢?这时就需要分情况来讨论了。


首先是一个线程写,多个线程读的情况,这种情况下不涉及并发写,只需要一个写线程更新完数据后其他读线程能够感知到变化即可,此时就可以使用volatile关键字了,该关键字可以保证指定内存对所有线程具有可见性,也就是当写线程更改了该区域内存时其他读线程是可以感知到变化的,同时该关键字带来的性能损耗在大多数情况下可以忽略不计,所以该方案是这种情况下的理论最优方案,可以考虑尽量使用该方案解决线程间的内存共享问题。


那么如果有多个线程写的情况呢?这种情况我们需要首先考虑是否可以从设计上避免,是否可以转换为一个线程写多个线程读的情况,如果确定不能,那么我们还是有对应的解决方案的。


在面对多线程写的情况下,可以采用加锁的方案来解决,锁也是有很多种的,如何保证性能尽量的高呢?以下是一些建议,可以参考:


1、首先是锁的选择,老版的JDK选择比较少,只有synchronize一种,而在较新的版本中(应该是1.5以后,记不太清),新增了java.util.concurrent包,其中包含了一个Lock接口以及对应的几种实现,那么对于这些锁应该如何选择呢?首先是业务场景,如果业务场景比较简单,那么尽量使用synchronize,因为该锁是通过关键字实现的,是java的内置特性,所以具有一些不错的特性,例如锁代码块退出自动释放锁、可以通过JMX监测是否死锁、使用简单等(至于那些说synchronize性能不行的,要不然就是从古代穿越过来的,要不然就是没用过较新的JDK,因为实际上JVM对synchronize在不停的优化,而在1.7和1.8(其他版本暂时没用过)中,synchronize的效率在同等条件下并不比Lock低),不过,上边也说了,synchronize适合简单的业务场景,那么复杂场景呢?这时就需要使用Lock接口了,Lock接口除了提供基本的synchronize的功能,还在此之上做了大量扩展,例如如果你希望你的程序如果在3秒内没有获取锁就放弃获取,synchronize是实现不了的,但是Lock可以,如果你希望你的锁可以在其他线程在某些条件下可以重入,而在另外一些条件下只能有一个线程获取锁,synchronize做不到,Lock仍然可以,如果你希望在等待锁的获取中该线程随时可以被中断,synchronize做不到,Lock可以(这种场景不是太常见),Lock提供的特性远不止这些,你都可以直接拿来使用,当然,Lock也是有一些不好的特性的,例如使用稍微复杂、不能使用JMX监测是否死锁、锁不会自动释放(所以为保证Lock锁的释放,一般都会单独加一个finally块来保证锁的释放)等等。具体如何使用就看你的业务场景了。


2、除了锁的选择,锁的粒度也是很重要的一块。如果你需要锁定的代码只有一行,而你却锁定了20行代码,那么这个锁的效率也是极低的,无论何时,保证锁粒度的最小化,这通常会给你带来一些甚至巨大的性能优化(但要保证锁的粒度不会小到使程序出错)。


3、可以使用java.util.concurrent包中提供的一些原子类来保证内存的正确共享,使用这些类也会避免使用锁,提高性能,这些类是以CAS为基础实现的,CAS全称compare and swap,从名字上就可以大概看出该操作是怎么实现的了,那就是比较和交换,具体的操作这里就不讲了,CAS相关的资料网上有很多,可以自行百度,这里只说一下理论上CAS并不是一个线程安全的操作,只是有系统底层的API保证了该操作的原子性,由此该操作也是线程安全的,所以可以看出语言的发展还是很依赖底层硬件方面的实现的。


总结:保证线程安全同时又照顾到性能,首选的是保证内存不共享,即各个线程只使用自己的栈内存而不是用堆内存(或者保证同一时间只会有一个线程使用某个位置的堆内存),如果不能保证可以通过java.util.concurrent提供的原子类来实现内存共享,如果这些原子类无法满足业务需求再考虑使用锁,简单需求可直接使用synchronize关键字,复杂的使用Lock接口,同时保证锁的粒度足够小,记住,无论何时,选用锁都是性能最差的解决方案。


PS:由于篇幅有限,所以只是简单介绍先线程安全方面的知识,有兴趣的可以深入的了解下java.util.concurrent包中提供的并发方面的工具,可以有效的提供多线程编程效率,同时弄懂里面的各个类的作用、原理以及使用场景,基本对并发就有个很不错的认识了,以后就是结合实际场景多使用多思考。同时这也是其他语言与java无法比拟的地方,java原生就对并发提供了很好的支持,跟我比并发?哼哼,你先比过Doug Lea老爷在再说~~


没有关注的可以扫下方二维码关注我,如果在使用过程中有任何问题还可以加我QQ1213812243询问~

另外征一名配图小伙伴,可以给文章做一个简单的配图,有意者可以联系我哦~



         

长按二维码关注我吧

不要错过


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值