线程安全性包括以下三点:

  • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、synchronized、Lock)
  • 可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)
  • 有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能保证他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则)

CAS (compare and swap)

CAS是JUC并发包的核心实现,本质是一种乐观锁,自旋锁,当多线程同时对某资源进行操作时,只能有一个线程操作成功,但不会阻塞其他线程而是通知它们操作失败,最底层调用的是native方法.

该算法有三个操作数,内存值V,旧的预期值A,要修改的新值B,当且仅当A和V的值相同时,才将内存值V修改为B,否则什么都不做.

相关应用: **自旋锁,**CAS操作失败时一直循环,直到成功为止.

在JAVA9之前,JUC的CAS操作是由Unsafe这个类来完成的,但是在java9之后变成了VarHandle这个类,主要是出于安全性和可移植性考虑.

Atomic包

在并发环境中,共享资源不能使用int,long double等基本类型,而是使用AtomicXXX等类,例如AtomicInteger,AtomicLong等的incrementAndGet ()方法.

AtomicXXX -> Unsafe类的compareAndSwapInt.


AtomicLong与LongAdder

Atomic包的CAS操作在竞争激烈的环境对于CPU的消耗过大,因此java8中新增了LongAdder,核心实现思想是热点分离.

原理: 高并发时将对单一变量的CAS操作分散为对cells数组中多个元素的CAS操作(通过hash判定操作哪个元素),取值时进行求和;低并发时仅对base变量(初始值V)进行CAS操作.

优缺点:

  • AtomicLong操作简单,适合并发度低或者对结果严谨的环境.
  • LongAdder操作复杂,适合高并发的环境,但是可能会出现统计有偏差的情况.

AtomicReference

作用是原子性更新对象引用.

AtomicIntegerFieldUpdater

作用是原子性更新该类的某个指定字段的值,该字段需要用volatile修饰且提供get方法.


CAS的ABA问题

某资源的初始值的A,一个线程把它修改为B,另一个线程又把它修改回A,这样该资源会被CAS判定为未修改过,但实际上却被修改过了.解决方法是AtomicStamperReference类,为资源增加版本标记,每次操作将版本号加1,进行CAS比较时不仅比较数值,也比较版本号.


两种锁:synchronized和Lock

区别在于:

  • 前者是关键字,后者为接口
  • synchronized在发生异常时,会自动释放锁,不会又死锁问题,而Lock不会释放,建议在finally中释放锁
  • Lock的子类包括可重入锁ReentrantLock,读写锁ReentrantReadWriteLock等,可通过lockInterruptibly()方法实现可中断性,通过构造方法来实现公平性等.
  • Lock能起到的作用更多,例如中断等待锁的线程,获知是否成功获取锁.
  • ReentrantReadWriteLock可通过读写分离提高并发效率,而synchronized在高并发下效率较低.

可重入锁

在Java中,synchronized和ReentrantLock属于可重入锁,即同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取该锁,不会造成死锁.


偏向锁/轻量级锁/重量级锁

这几个是描述锁的状态,是JVM底层与synchronized的实现有关的概念,从1.6开始优化synchronized而引入的锁机制,原理比较复杂,目前也不太需要关心,使用JVM默认的策略即可.


可见性-volatile

通过加入内存屏障和禁止重排序优化来实现

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存中.(写完后及时保存)
  • 对volatile变量读操作时,会在读操 作前加入一条load屏障指令,从主内存中读取共享变量.(读之前先更新)

有序性 - happens-before原则

共8条,意思大概是A操作必须先于B操作,太长不想记.


线程封闭

  • ad-hoc 线程封闭 : 程序控制实现,最糟糕,忽略
  • 堆栈封闭 : 局部变量,无并发问题
  • ThreadLocal : 如果多线程需要操作的是全局变量而非线程内部的变量,可以用threadlocal进行封闭.应用有数据库连接池,获取connection

StringBuffer与StringBuilder与String

string为final类,绝对的线程安全.但是每个string都是一个新的对象,内存消耗大.

string重写了equals和hashcode方法,两个字符串比较内容而不是地址.

单线程下的效率builder>buffer>string,多线程下修改字符串首选buffer.

StringBuffer如何实现线程安全?

在append等方法上使用synchronized修饰.


线程不安全类与写法

  • StringBuilder -> StringBuffer
  • SimpleDateFormat -> joda-time or java8的日期api
  • ArrayList,HashSet,HashMap等
  • 非原子性操作,例如先检查再执行: if( condition(a) ) { handle(a); }

同步容器与并发容器

同步容器包括HashTable,Vector等,是java早期对于线程安全的解决方案,能保证线程安全,但不保证并发效率.如今不推荐使用

并发容器包括JUC包下的ConcurrentXXX等工具类