java 并发基础

最近一直在看《java并发编程的艺术》这本书,书是好书,就是看起来太费劲。这就是所谓的技术门槛吧。

也不知道理解的对不对,我就随便输出点东西吧,因为我发现不输出,不把自己掌握的东西做个定性,就始终不是自己的。

先说为啥要有并发吧,还不是因为出现了多线程技术,搞什么多线程充分利用cpu,说白了就是压榨机器性能。如果同时执行的线程大家各干各的,互不干涉也就没什么事了,偏偏有些时候是多个线程执行同一个任务,或者执行不同的任务,要竞争同一个资源;结果就导致多个线程执行出来的结果与我们的预期不一致。这肯定是不行的。为什么会出现这种情况呢?有两个原因:一是CPU以及内存存在缓存,各个线程处理临界资源时,实际是先把临界资源COPY到自己的工作区,处理完后在写回内存,在这个过程中别的线程也做了操作,不一致就出现了。二是java虚拟机在执行java代码时,会根据硬件环境对字节码进行优化,在这个过程中,一些指令会进行重排序,(重排序的目的还是为了充分利用CPU的时钟周期)单线程情况下,不会影响执行结果,多线程情况下,如果不做特殊处理,就会出现跟预期不符的结果。

那么所谓的java并发编程要做什么呢?通过上面的介绍,我们可以下结论了:java并发编程就是在我们充分了解了机器的特性之后,通过编程手段,一方面充分利用java的并发特性写出高性能的代码,另一方面还要使用java提供给我们的各种语言特性,来规避并发过程中的问题,让程序并发的结果符合我们的预期。

再进一步说:如何让并发结果符合我们的预期,其实就是对多线程交叉地带进行管制,让大家有序的访问临界资源。

java并发的基础是基于两点的:1)是大家比较熟悉的synchronized;就是同步锁。一般上认为synchronized是一把重量级锁,虽然实现了并发控制,但是控制的太死,就好比高速下口的人工收费岗,车子高速跑过来不得不等上几十秒,势必造成拥堵;2)volatile变量,这个单词意思为不稳定的。在硬件上来讲就是CPU对变量所做的修改操作会马上回写到主内存中,同时使其他CPU持有的副本失效。为什么是CPU呢,因为通常所说的缓存其实就是CPU缓存,主内存只有一个大家一样的。

先说synchronized。如果要实现对某个对象的同步访问,或着某个方法要访问一个临界资源,通常的做法就是在对象或者方法前面加上synchronized关键字。这个关键字起作用的基础是object对象的头部有一个监视器monitor,线程需要访问对象时先检查monitor的值,如果是1说明里面有人,就会进入阻塞队列;如果是0则先修改monitor,在进入monitorenter,执行完毕或执行一定时间后monitorexit释放锁,唤醒阻塞队列中的一个或所有线程,让别人执行。

这里要说明一个概念:线程的执行状态,创建,阻塞,就绪,执行,死亡。阻塞,就是即没有获得资源,也没有获得CPU,就绪是获得了资源,没有获得CPU,执行是既有资源,也有CPU。所以从阻塞到执行,中间还隔着一个就绪状态。这也是为什么说synchronized是一把重锁的原因。使用synchronized关键字加锁时,没有获得资源的线程进入阻塞状态,用户态跟假死一样,即不能响应用户操作,也不会执行任务,只能被动等待临界资源上的唤醒操作notify,nofityAll。在某些状态下,这是很不可接收的。这就引出了轻量级锁。

重量级锁的本质是依托JVM中object对象提供的对象头数据结构。那么轻量级锁的本质是什么呢?就是上面说过的volatile。volatile的特性就是修改会马上写会主内存,即内存修改的可见性。这个特性是由JVM解释字节码为CPU指令时进行保障的。这个修改的可见性只是指修改之后可见,但往往修改的过程不是简单的赋值或者++,--,即便是自增自减这种操作,底层也不是一个指令就能完成的,即不是原子操作。这就引出了一个问题,volatile只对结果操作这一步的可见性负责,其他操作是不保证同步的。所以,使用volatile时会强调一点,它适合于的场景:1)对变量的写操作不依赖于当前值;2)该变量没有包含在其他变量的不变式中。

说起轻量级锁,就不得不提JUC同步框架,可以说JUC是建立在volatile关键字上的一套轻量级锁框架。相对于synchronized关键字的悲观,笨重来说,它是一种乐观,灵活的锁机制。比如使用volatile作为信号量(相对于object的monitor),使用CAS进行简单赋值(乐观锁机制),使用AQS作为同步队列(相对于object的阻塞队列)。

20180126补充:这两天看了源码对轻量级又有了一些认识:synchronized内部的同步机制是JDK中封装好的,使用关键字之后,我们无法进行干预,就好比我们想装个xx浏览器,听说这玩意挺安全的嘛,结果一个确定键点下去,xx卫士,xx杀毒,xx输入法,全给你装上了(此处使用了夸张手法)。可是我只是想上一下1024而已啊。好了,轻量级锁的另外一个含义就是,我把原来系统打包的功能,拆成一个一个小功能,这样你既可以根据需要选择功能,还可以在功能之间加上自己的逻辑。从软件设计角度讲,功能更原子,复用性更高,可定制性更强。摘自JDKAPI:Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。condition,条件,也称条件队列。

另外,在J.U.C.里面大量使用了内部类。AQS,抽象队列同步器是一个抽象类,JUC里面的各种锁,比如ReentrantLock ,ReentrantReadWriteLock,都在内部实现了AQS。更高级一些的应用比如CyclicBarrier,则是在内部定义了ReentrantLock借以实现更高级的功能。然后,内部类到底有什么用呢?首先,封装,我把同步器Sync定义在ReentrantLock里,实现了封装,Sync还是一个抽象类,我根据需要再定义Sync的两个实现类,一个公平同步器,一个非公平同步器。然后,隐藏实现细节,这些内部类我是不希望别人看到的,因为使用者本身不需要知道我的实现细节。然后,多继承,比如ReentrantLock是一个Lock,我实现Lock接口就可以了。我要使用同步器,同步器是一个抽象类,我要继承它我就变成同步器了。好,我用内部类继承它,外部类可以直接访问内部类的所有属性和方法,包括私有的,又不用改变自己的性质。在这里,内部类都定义成了静态内部类,跟静态变量静态属性一样,在类加载时就初始化好了。

以往大家都比较实在,到营业厅办业务排队,发现窗口前面有人,就领个号就到休息区等了,窗口的人办完出来,喊一嗓子再小跑过来上号;这虽然保证了秩序,但是效率有损失,我只能等叫我号了我才过来,我走的远了(等待过久资源被从内存置换出去)走过来就几分钟过去了。每个人都这样,整个系统效率都被拖慢了。后来有人就说我不去休息去了,我就站在旁边等,我觉得你马上就办完了,我就在门口等着,一会看一看窗口有人没?一旦发现前面没人了,立马冲进去,省了之前的间隙时间(状态恢复时间),整个系统的吞吐率提高了不少。当然要是前面这人业务复杂(长任务),用了一上午时间,你要站在原地等就太累人了(自旋等待,浪费CPU),不如去休息区等待划算了。


未完待续。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值