线程安全

什么是线程安全?咱们用《java concurrency in practice 》中的一句话来表述:当多个线程访问一个对象时,若是不用考虑这些线程在运行时环境下的调度和交替执行,也不须要进行额外的同步,或者在调用方进行任何其它的协调操做,调用这个对象的行为均可以得到正确的结果,那这个对象就是线程安全的。从这句话中咱们能够知道几层意思: 1.线程安全是和对象密切绑定的;2.线程的安全性是因为线程调度和交替执行形成的;3.线程安全的目的是实现正确的结果。咱们在下面的详细的内容中会反复引用这个定义来讲明对象的线程安全性。html

1、安全性

咱们能够按照java共享对象的安全性,将线程安全分为五个等级:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立;java

1.1 不可变

在java中Immutable(不可变)对象必定是线程安全的,这是由于线程的调度和交替执行不会对对象形成任何改变。一样不可变的还有自定义常量(final及常池中的对象一样都是不可变的。api

1.2 绝对线程安全

即这些对象不管在任何环境下进行线程调度或交替执行都不会影响数据的正确性。实际上,要实现对象的绝对安全要付出的代价,它要应对各类环境和的不肯定性;甚至这种绝对性是不可能实现的,所以,在实际应用当中,javaApi 中不存在绝对线程安全的对象。数组

1.3 相对线程安全

在java api中咱们一般所说的线程安全都是相对的线程安全。以下面的示例如示安全

public class ThreadSafeTest {
    public static Vector<Integer> num =  new Vector<>();

    public static void main (String[] args) {
        while (true) {
            for (int i = 0; i < 100; i++) {
                num.add(i);
            }

            // 从后往前remove
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < num.size(); i++) {
                        num.remove(i);
                    }
                }
            });

            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < num.size(); i++) {
                        System.out.println(num.get(i));
                    }
                }
            });

            t1.start();
            t2.start();

        }
    }
}复制代码

咱们知道vector 是java线程安全的数组,t1 和 t2 都有可能报出数组溢出的异常。可能你们也知道缘由:在进行一次for循环的过程当中,若是发生中断,则num.size()的大小是会变化的,当尝试去get时,可能正好被 remove掉了。也就是说单独去get或者是remove时,vector 是线程安全的,但组合使用却不是线程安全的,因此vector的线程安全是有条件的,也就是相对线程安全。固然对于java 的concurrent包也是如此。bash

1.4 线程兼容

对象自己并非线程安全的,但在多线程环境中,客户端能够经过正确地使用同步手段来达到线程安全。咱们在java使用的大部分非线程安全的api都属于这种,例如hashmap ,arraylist等。多线程

1.5 线程对立

线程对立指在多并发环境中,不管采起任何同步措施都不该该同时出现的代码。好比Thread suspend(中断)和resume(恢复),若是前一个线程进行了中断,另一个线程尝试resume时,则获取不到该对象,则会产生死锁。(www.zhihu.com/question/40…架构

2、同步

影响对象线程安全是在多并发状况下,多个线程之间的调度和交替执行,影响对象的状态,那如何实现多线程之间的数据同步将是解决线程安全的关键所在。并发

2.1 互斥同步

互斥同步即当一个线程在对对象进行操做时,其它线程没法对该对象作任何操做。实现互斥同步有多种方法,如临界区、互斥量、信号量都是实现互斥同步的方式。
java实现同步的主要手段是使用synchronized关键字,它通过编译以后,会在同步块先后分别造成monitorenter 和 monitorexit 两个字 节码,这两个字节码都须要一个reference类型的对参数,来讲明须要加锁和解锁的对象。如synchronized 没有指定锁定的对象,则指向该类对应的对象或者类自己(若是是静态类)。当执行monitorenter时,锁计数器就会加1,相反当 执行monitorexit时,计数器减1,当计数器为0时,就会释放该对象。固然, synchronized 对同一条线程是可重入的,以免自已被本身锁死的状况。
除synchronized 外,java.util.concurrent 包还提供了重入锁(ReentrantLock)来实现同步, 重入锁提供了更加丰富的功能:
等待可中断:指当持有锁的线程长期不释放锁时,则放弃等待转而作其它事情
公平锁 :能够按照时间顺序获取锁(但默认实现仍然是非公平锁)
锁绑定多个条件ide

对synchronized 和ReentrantLock 的更详细的对比参考:blog.csdn.net/fw0124/arti…
互斥同步属于较重的同步,它的性能也相对比较低。由于,对两个线程对同一个对象的访问,不管是否会存在数据同步的问题,都要进行锁竞争,它属于一种悲观锁。线程等待锁释放的阻塞过程严重下降了代码的运行效率。

2.2 非阻塞同步

为了解决互斥同步的线程阻塞问题,产生了非阻塞同步。非阻塞同步属于乐观锁:基于冲突检测的乐观并发策略,通谷地说,它是先进行操做,若是没有其它线程争用,则操做成功;若是有其它线程争用,产生了冲突,则进行冲突补偿。乐观锁的出现是在硬件发展的基础之上产生的,为何这么说呢,这是由于须要保证咱们对数据的操做和冲突检测具备原子性,这须要硬件的单一指令来实现。最多见的指令是(compare-and-swap)(详细介绍见:www.cnblogs.com/Mainz/p/354…
非阻塞同步并非要替代互斥同步,只是互斥同步的一个补充,缘由在于它的高性能。但因为其在设计上的缺陷(ABA问题),并不能成为互斥同步的一个替代品。

2.3 无同步

并非全部的数据都须要同步,若是不存在数据争用就不须要进行同步。
另外,针对部分情境(共享数据的代码在一条线程内执行)也可使用线程本地存储(Thread Local storage),它特别适用于消费队列的架构模式。

3、锁

大部分的数据同步的基础仍然是互斥同步,咱们从上面能够知道互斥同步在进行锁竞争时,线程会被阻塞,并在挂起和唤醒中不断切换,这种线程切换对性能的影响是比较大的。因为以上缘由,锁优化就成了JAVA的重要工做,下面我就来看一下有哪些常见的锁优化。

3.1 自旋锁与自适应自旋

在大部分状况下,当一个线程未竞争到锁时,它一般只须要等待很短的时候,就能从新得到锁,因此java 就采用循环忙等待的方式来等待锁的释放,而不是挂起和恢复线程。可是忙等待也不是无损失的,它须要占用cpu资源,若是等待了很长时间,锁也得不到释放,也是很可怕的资源浪费。
因此在1.4时,能够经过参数控制自旋次数,超过次数线程就会被挂起。在1.6时,java则采用了自适应的方式来控制自旋次数,而不须要经过人工进行设置。

3.2 锁消除

锁消除是指对于JVM 即时编译时,具备代码同步要求,但实际上共享数据并不存在竞争状况的锁进行消除。共享数据是否存在竞争,依赖JVM的逃逸分析技术(www.importnew.com/23150.html)

3.3 锁粗化

虽然不少时候咱们都但愿对锁的粒度进行细化,以减小锁竞争的代码范围;但当,一个代码块须要用多个锁进行同步时,则能够考虑使用一个锁对整个代码块进行锁定,以减小锁的竞争开销。

3.4 轻量级锁

轻量级锁是相对于重量级锁而言的,它假设数据不存在竞争的状况下,来提高锁的性能。提高锁性能的方式,就是摒弃重量级锁利用系统互斥量来对对象加锁,而是采用对象头部的标志位和cas操做来进行锁竞争检测。具体的实现原理参考:
my.oschina.net/u/140462/bl…

3.5 偏向锁

偏向锁相似于轻量级锁 是对无竞争状况下的优化。因为轻量级锁仍然须要对markword进行同步,而偏向锁则是消除这种同步,进一步地优化性能。偏向锁经过在markword中写入threadId和标志位,来对对象锁定,若是在对对象的操做过程当中,其markword部分未被更改,则表示不存在竞争,也就无需同步。若是被更改,则说明存在竞争,会上升到轻量级锁或重量级锁进行同步操做。偏向锁的实现原理参考:my.oschina.net/u/140462/bl…