java代码先编译成字节码,字节码最后编译成cpu指令,因此Java的多线程实现最终依赖于jvm和cpu的实现
synchronized和volatile
我们先来讨论一下volatile关键字的作用以及实现机制,每个线程看到的用volatile修饰的变量的值都是最新的,更深入的解释就涉及到Java的内存模型了,我们知道Java将内存分为主内存和线程私有内存,所有的全局变量都在主内存中,每个线程使用变量时都会从主内存中读取变量,然后放到各自线程的私有内存中,这样线程使用变量时就不用每次都去读取主内存了,当然这也产生了一个问题,如果线程修改了变量值,但是修改的值没有及时地同步到主内存中,那么其他线程看到的变量值仍然是未修改之前的值,这就产生了并发问题,而当这个变量用volatile修饰后,每次线程都会从主内存中读取变量值,也就是说抛弃了线程私有内存中的变量值,而线程每次修改变量后,就会将修改后的值同步到主内存中去。
理论上volatile就是这么实现滴,但是volatile最终会被编译成机器指令,所以volatile这种机制也需要相关的机器指令支持,学习过计算机组成原理的同学们都知道计算机在cpu和主内存直接有一个cache缓存,是不是和Java模型很类似,当然还不一样,其实volatile最终使用了cpu缓存一致性也就是说将缓存中的内容立刻更新到主内存中去,同时将其他缓存中的值置为无效。如果大家有探索精神可以看看看看volatile被编程为的汇编代码,就会发现volatile是用Lock信号实现地。
下面我们再来说说synchronized关键字,学习过Java的同学应该都知道这个关键字是用来实现多线程同步的,synchronized的具体用法这里就不介绍了,我们来聊下这个关键字的具体实现,看过Java并发的同学都会发现synchronized被称为重量级锁,怎么理解这个重量级的概念那?反正我的理解是加锁解锁耗费地时间多,导致并发度比较低呗,但是随着JDK版本的升级,synchronized的性能和并发库中Lock的性能基本持平。好了言归正传,synchronized的具体实现不知道同学们了解多少,我想大部分人应该能说出synchronized由一个同步队列和对象监视器实现,线程进入同步块时,先获取监视器如果成功就进入同步块,如果不成功就进入同步队列等待另外一个线程从同步块退出,没错这就是synchronized的实现,如果你只知道这些说明你了解的还不够深入,因为你还需要知道偏向锁、轻量级锁和synchronized的关系,在这里我就抛砖引玉先说说我自己的理解吧,我们都知道一个Java对象有三部分组成,对象头,实体部分,对齐填充部分,这个对象头就是实现synchronized的关键,在比较老的JDK版本中,一个线程进入同步代码块时就要获取互斥量,不管有没有其他线程,改进后的synchronized,线程一般先获取偏向锁,如果有竞争就膨胀为轻量级锁或者重量级锁,轻量级锁又会膨胀为重量级锁,那么偏向锁、轻量级锁、重量级锁有什么区别那?
偏向锁:
对象头中设置偏向锁标记,同时将当前线程的线程id保存在对象头和栈的锁记录空间中;
轻量级锁
将对象头的内容复制到当前线程栈的锁记录空间中,同时将对象头设置为指向锁记录空间的指针;
重量级锁
将对象头的内容复制到当前线程的锁记录空间中,同时将对象头设置为指向互斥量的指针;
当一个线程进入同步快时,先通过cas设置对象头为偏向锁,若设置成功则说明线程获取锁,若设置不成功,说明锁存在竞争,持有偏向锁的线程就要释放偏向锁,偏向锁膨胀为轻量级锁或重量级锁。
当一个线程持有轻量级锁,另外一个线程尝试获取锁,获取失败,则另外一个线程会自旋等待,若等待一段时间仍未获取锁,则线程进入等待,持有轻量级锁的线程就会在安全点处释放轻量级锁,轻量级锁也会膨胀为重量级锁。
当一个线程持有重量级锁时,另外一个线程就会被直接踢到同步队列中等待。
(安全点是jvm的一些特殊位置,在这个位置上所有的线程都会暂停工作,一般在安全点处进行垃圾回收,还有一个概念是安全区域,安全区域是指在一块代码内引用关系不会发生变化,这个代码的任何位置进行垃圾回收都是可以的)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。