您的位置:首页 > 编程语言 > Java开发

深入理解Java虚拟机JVM高级特性与最佳实践阅读总结—— 第十三章 线程安全与锁优化

2017-03-02 10:50 886 查看
线程安全,当多个线程访问同一个对象时,无论系统的调度方式、线程的交替执行方式、也不需要额外的同步手段、调用方法时不需要额外的协同,调用这个对象的都可以得到正确的结果

按照线程安全的强弱,分为五大类
          1、不可变Immutable,不可变对象一定是线程安全的,对象的方法实现和调用都不需要同步;前提是不可变对象被正确创建(不发生this引用逃逸),事实不可变对象也是线程安全的;Java中,如果共享的数据是基本类型,用final修饰,如果是对象,需要保证该对象的行为不会改变对象的状态,如String、大数类、包装类等
          2、绝对线程安全,满足上述线程安全的定义的类;注意Java中标注线程安全的类,大多不是绝对线程安全的,如vector类
          3、相对线程安全,即一般意义上的线程安全,保证对对象的单独操作时安全的,不需要额外的同步,但是对于一些连续调用,仍需要在调用端使用同步手段;Java中线程安全的类大多是相对线程安全的,如vector、hashtable、collections中的同步集合类
          4、线程兼容,对象本身不是线程安全的,但是在调用端使用一定的同步手段,仍然可以实现线程安全,Java中大多数类就是如此,如ArrayList、HashMap
          5、线程对立,无论调用端是否使用同步,都不能保证并发使用的代码;如Java的suspend方法与resume方法,因为两者都是独占的、不会释放锁

线程安全的实现方式
          1、互斥同步(mutual exclusion & synchronization),在多个线程并发访问共享数据时,只允许同一时刻内只被一个(如果是信号量,可以为几个)线程使用,临界区Critical Section、互斥量Mutex、信号量Semaphore都是互斥的实现方式;互斥同步也称阻塞同步,是一种悲观的同步方式,会带了线程阻塞和唤醒的性能问题;Java中互斥手段主要通过synchronized关键字实现,并且synchronized同步块是可重入的,不会出现自己锁死自己的情况;由于Java线程是映射到系统的原生线程上的,则线程的阻塞、唤醒需要系统的参与,即需要从用户态切换到内核态,而这种转换代价是昂贵的,synchronized是一个重量级的操作,也可以使用并发包下的ReentrantLock类,相对于synchronized关键字,具有以下三个优点:
                    1、等待可中断,如果持有锁的线程长时间不释放锁,等待线程可以放弃等待
                    2、公平锁,多个线程等待同一个锁时,按照申请顺序依次获得锁,默认是非公平的,synchronized锁也是非公平的
                    3、锁绑定多个条件,可以绑定多个Condition对象,实现多路选择通知,而synchronized是随机的
                    4、性能上,ReentrantLock优于synchronized,但1.6的优化逐渐弱化了这种差距
          2、非阻塞同步(Non-Blocking Synchronized),一种基于冲突检测的、乐观的同步方式,先进行操作,如果没有其他线程争用共享数据则操作成功,否则就产生了冲突,需要采取补偿措施,如不断重试直至成功;该同步方式需要硬件指令的支持,即保证上述中的操作与检测是原子的;例如1.5中支持的CAS(Compare And Swap,CAS需要三个变量,内存地址V、旧的预期值A、新值B,当且仅当V中的值等于A时采用B更新V的值,否则等待,无论是否更新,总返回A),该操作在java.misc.Unsafe中,但是只能有引导类加载器使用,可以使用并发包下的整数原子类,其中的compareAndSet()、getAndIncrement()就是使用了CAS
          3、无同步方案,同步保证的共享数据争用时的正确性,如果一个方法不涉及共享数据,则不需要同步
                    1、可重入代码,不依赖堆上的数据、公共系统资源,状态由资源传入,不调用不可重入的代码;可重入的代码都是线程安全的,但是线程安全的代码不一定是可重入的
                    2、线程本地存储,把共享数据的可见范围限制在一个线程之内;例如ThreadLocal类,是当前线程的ThreadLocalMap对象的的访问入口,ThreadLocalMap是一种叫键值对的数据结构,键是TreadLoacal.ThreadLocalHashCode

锁优化,在线程之间更高效的共享数据、解决竞争行为,提高运行效率:
          1、自旋锁,为了让线程等待锁而不放弃CPU时间,让线程执行一个忙循环(自旋);如果锁占用时间很短,则自旋等待效果很好,反之则浪费了CPU时间;特点是自旋锁虽然避免了线程切换的开销,但是也浪费了CPU时间;可以通过-XX:PreBlockSpin修改自旋等待次数
          2、自适应自旋锁,不再采用固定时间,根据前一次在同一个锁上自旋时间和锁拥有这状态决定本次自旋时间;如果上次自旋成功获得锁并正在运行,则认为本次自旋也可能再次成功,进而允许自旋等待持续相对较长的时间;如果自旋很少成功过,则以后获取锁的时候可能忽略自旋;
          3、锁消除,对一些代码上要求同步,而通过逃逸分析发现共享数据不存在竞争,则消除该锁;堆上的数据如果不会逃逸出去被别的线程访问,就可以将其视为栈上的数据,看成是线程私有的
          4、锁粗化,一般推荐同步范围控制的小一点——只在共享数据的实际作用域进行同步,以尽可能较少同步操作的数量,在存在锁竞争条件下等待线程尽可能快的拿到锁;而如果对一系列的操作反复加锁、解锁,虚拟机将同步范围扩大(锁粗化);
          5、轻量级锁,在没有多线程竞争的情况下减少重量级锁使用系统互斥量产生的性能消耗(依据是大部分锁在同步周期内是不存在竞争的),使用的是CAS操作实现加锁解锁
          6、偏向锁,锁偏向第一个获得他的线程,如果在接下来的执行过程中,锁没有被其他线程获取,则持有偏向锁的线程永远不需要再进行同步;通过CAS操作实现(第一次获得锁的时候),可以提高带有同步但无竞争的程序的性能

轻量级锁和偏向锁,轻量级锁是在无竞争的情况下使用CAS操作消除同步使用的互斥量;偏向锁则是在无竞争条件下,把整个同步消除掉,连CAS操作也省略;两者都需要通过对象头的Mark Word来实现
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐