JAVA - 并发编程 - 线程安全方案

本文主要介绍 java中在并发环境下,有哪些方案实现线程安全,注意 线程问题主要由内存模型引起的(在基础篇中)

目录

  valotile可见性

  CAS无锁编程

  内置锁(同步关键字synchronied)

  显示锁(lock)

valotile可见性

  1 volatile关键字的两层语义(一旦一个共享变量被volatile修饰)

    1.1 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其它线程来说是可见的

    1.2 禁止进行指令冲排序

  2 实现原理(加lock前缀,进行内存屏蔽)

    2.1 确保指令重排序时不会把其后面的指令排到内存屏蔽之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏蔽这句指令时,在它前面的操作已经全部完成

    2.2 它会强制将对缓存的修改操作立即写入主存

    2.3 如果是写操作,它会导致其它CPU中对应的缓存行无效

  3 使用场景(需保证操作时的原子性)

    3.1 对变量的写操作不依赖与当前值

    3.2 该变量没有包含在具有其它变量的不变式中

  4 总结 可见性,不保证原子性,一定程度上保证有序性

CAS无锁编程

  1 什么是CAS无锁编程?

    CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B,并且仅当A=V时,将内存值V修改成B,否则什么都不做

  2 为什么会出现CAS无锁这个概念以及机制(基于锁机制的缺陷)

    2.1 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题

    2.2 一个线程持有锁会导致其它所有需要此锁的线程挂起

    2.3 如果一个优先级高的线程等待一个优先级低的线程加锁会导致优先级倒置,引起性能问题

  3 CAS的缺陷

    3.1 ABA问题 如果一个值原来是A,编程了B,又编程了A,那么使用CAS进行检查时会发现它的值没有发生变化(解决方法:使用版本号)

    3.2 循环时间长,开销大,自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销

    3.3 只能保证一个共享变量的原子操作

  4 JAVA对CAS的支持 (Atomic)

内置锁(同步关键字synchronied)

修饰方式 作用范围 作用对象 锁类型 代码
代码块 大括号{}括起来的代码 调用这个代码块的对象 对象锁

synchronied(this) {}

方法   整个方法 调用这个方法的对象 对象锁 synchronied  void method(){}
静态方法 整个静态方法 这个类的所有对象 类锁 synchronied static void method(){}
类   关键字后面括起来的部分 这个类的所有对象 类锁 synchronied(类名.class){}

 

     

  1 规则 (类锁和对象锁)  对象锁(同对象互斥,不同对象不互斥(重入)) 类锁(互斥) 对象锁和类锁不互斥,非同步方法不影响

    1.1 当两个并发线程访问同一个对象中的synchronized(this)(对象锁)同步代码块时,一段时间只能有一个线程得到执行,而另一个线程只有等当前线程执行完以后才能执行这块代码

    1.2 当一个线程访问对象的一个synchronized(this)(对象锁)同步线程块时,其它线程仍可以访问这个对象中其它非synchronized(this) 代码块

    1.3 当一个线程访问对象的一个synchronized(this)(对象锁)代码块,其它线程对这个对象中其它synchronized(this)同步代码块的访问将被阻塞

显示锁(lock)

  1 Lock接口

  2 ReentrantLock 重入锁 提供了与同步关键字相同的互斥和内存可见性的保证

  3 为什么java要提供与同步关键字如此相似的锁

    3.1 内部锁不同中断那些正在等待获取锁的进程,并且在请求锁失败的情况下,线程必须无限等待

    3.2 内存锁必须在获取它们的代码块中被释放,这很好的简化了代码,但是在某些情况下,一个更灵活的加锁机制提供了更好的活跃度和性能

  4 ReentrantLock的优点  可以轮询和可定时的锁请求,可中断的锁获取操作

  5 ReentrantReadWriteLock 读写锁 特性 读-读不互斥,读-写互斥,写-写互斥)

    5.1 readLock() 读锁

    5.2 writeLock() 写锁 

  6 Lock和synchronized的选择

    6.1 Lock是一个接口,而synchronized是java中的关键字,内置的语言实现

    6.2 synchronized发生异常时,会自动释放线程占用的锁,而Lock在发生异常,需要在finally块中释放锁,否则很可能造成死锁现象

    6.3 Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去 

    6.4 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到 

    6.5 Lock可以提高多个线程进行读操作的效率 

 

 

 

 

 

 

 

猜你喜欢

转载自www.cnblogs.com/hpzhu/p/9125281.html