主要包括两个方面的内容一个是从程序员的角度如何写线程安全的代码,另外一个是虚拟机底层如何实现线程安全。程序员
若是多个线程一块儿读写一个共享的数据,在不加额外措施的状况下必定会产生并发问题,这是一个老生常谈的问题了。解决的方式也有不少,这里主要从是否阻塞相关线程的角度分为两类。安全
一、互斥同步多线程
我更倾向于把他理解成阻塞同步。最经常使用的关键字是synchronized,该关键字编译后会在同步的代码块先后增长monitorenter和monitorexit关键字,这两个关键字都须要一个reference类型的变量来指定一个对象。从这个角度能够理解sychronized是针对对象的。若是指明了sychronized是针对某一个对象的,那么reference就会指向该对象,若是没有的话根据sychronized指向的是类方法仍是实例方法去指向相应类对象或者实例对象。并发
该关键字会带来互斥性,即任意一个时刻只能有一个线程能够得到锁,若是没有成功得到那么试图得到线程的锁会被阻塞知道锁的释放。优化
该锁是能够重入的,即一个线程在已经得到锁的前提下再次尝试得到锁的时候是能够得到锁的,只不过锁的计数器会加一。相应的一个线程在执行monitorexit以后锁的计数器会减一,当计数器为0的时候锁被释放而后唤醒别的在等待的线程。操作系统
线程A得到锁指的是reference所指向的对象的对象头里指向A线程。线程
Java的线程是依赖于操做系统的线程来实现的,因此一个Java里的线程就是操做系统里的一个线程,JMM没有对线程作抽象。因此线程的阻塞与唤醒须要陷入内核态(内核线程?????)这回带来一个较大的开销,早期的JDK会使用concurrent包中的ReentrantLock来实现同步。对象
ReentrantLock相较于sychronized有三个特色:一、等待可中断,即一个线程在等待锁的时候能够选择放弃等待。 二、能够实现公平锁。所谓公平锁指的是按照申请锁的时间的顺序来得到锁,虽然sychronized和ReentrantLock都是非公平的,可是ReentrantLock在构造的时候能够设置成公平的 三、能够绑定多个条件blog
二、非阻塞同步内存
阻塞同步须要把相关线程阻塞起来,阻塞和唤醒线程须要较大的代价。随着硬件指令集的发展示在有了基于冲突检测的乐观并发策略。
这里介绍了一个CAS(Compare-and-Swap)操做。CAS操做须要三个操做数:内存的地址V,旧的预期值A,新的预期值B。CAS执行的时候会比较V和A的值是否相等,只有他们相等的时候会把B操做的值赋给V操做不然不更新。这个指令看似干了不少事情,可是经过一些硬件的实现保证这是一个原子级别的操做。
Java里的Concurrent包里不少都是用CAS来实现的,好比AtomicInteger。
当增长一个AtomicInteger的时候,会执行一个死循环用当前的值作A,A+1作B,而后直到CAS操做成功才推出该循环。这样即便这个increment被打断的时候,实时上他很大程度上会被打断由于没有加锁,也不会被影响其正确性,固然虽然没有讲我猜想上面三个语句都是原子的,即curent赋值、next赋值、CAS操做是原子的。只要能保证这三个操做是原子的,那么整个自增方法即便不是原子的,也能保证在多线程的条件下自增是线程安全的。
互斥锁的一个很大的缺点在于线程的阻塞和唤醒须要较大的代价,一个解决思路是CAS,这里提供了另外一种思路。当一个线程试图得到一个锁且失败的时候,他不是被挂起,而是执行一个自旋即忙等待。若是持有锁的线程能很快释放锁的话,那么自旋锁的代价是小于互斥锁的。
为了不一个线程长时间自旋带来的浪费,每每设定一个阈值,当自旋超过必定次数的状况下就再也不自旋转而变成互斥锁。根据如何设置阈值的不一样能够把锁分为自适应锁和非自适应锁,非自适应锁的阈值是设定好的,自适应锁如其名所言,自旋的阈值是根据上一个在该锁上自旋的线程决定的,即若是上一个线程在这个锁上自旋了好久,那么个人阈值就大一些。
虚拟机分析一些锁的必要性,若是是一个不须要加锁的地方加了锁那么在编译的时候会去处。
若是一串连续的操做都是对同一对象加锁,那么虚拟机会把加锁的对象粗化对整个序列开头和结尾加一个范围更大的锁。