并发是指同一个时间段内多个任务同时都在执行
并行是说在单位时间内多个任务同时在执行
线程的工作内存展示
Java中的synchronized关键字
synchronized块是Java提供的一种原子性内置锁,java中每个对象都可以把他当做一个同步锁来使用。内置锁是排他锁,当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取锁。
进入synchronized块的内存语义是把在synchronized块中使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量就不会送线程的工作内存中获取,而是直接从主内存中获取,退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。
synchronized关键字会引起线程上下文切换并带来线程调度开销。
Java中的volatile关键字
当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。该关键字可以确保对一个变量的更新对其他线程马上可见。其他线程读取该共享变量时会从主内存重新获取最新值。
当写入变量值不依赖变量当前值时,可以使用volatile关键字,如果依赖当前值,需要获取-计算-写入三步操作,这三步操作不是原子性的,因此volatile不保证原子性。
Java中的原子性操作
原子性操作是指执行一系列操作时,这些操作要么全部执行,要么全部不执行,不存在只执行一部分的情况。
使用synchronized关键字可以实现线程安全性,即内存可见性和原子性。
Java中的CAS操作
CAS即Compare and Swap,是JDK提供的非阻塞原子性操作,通过硬件保证比较-更新操作的原子性。
Unsafe类的重要方法
JDK的rt.jar包中的方法Unsafe类提供了硬件级别的原子性操作。
锁的概述
悲观锁
悲观锁对数据被外界修改持保守态度,认为数据很容易会被其他线程修改,所以在数据被处理前对数据加锁,并在整个数据处理过程中,使数据处于锁定状态。
乐观锁
乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。
公平锁与非公平锁
这两类划分依据是根据线程获取锁的抢占机制,公平锁表示线程获取锁的顺序是按照线程请求锁的时间早晚决定,先来先得,非公平锁则是在运行时闯入就是先来不一定先得。
独占锁与共享锁
根据锁只能被单个线程持有还是能被多个线程共同持有分为独占锁和共享锁。
独占锁:一种悲观锁,保证任何时候只有一个线程能得到锁,每次访问资源都先加上互斥锁,同一时间只允许一个线程读取数据。
共享锁:一种乐观锁,允许多个线程同时进行读操作。
可重入锁:如果一个线程再次获取它自己已经获取的锁时是否会被阻塞,如果不被阻塞,就是可重入的。(synchronized的锁时可重入锁)
自旋锁:在当前线程获取锁时,如果发现锁已经被其他线程占用,它不马上阻塞自己,在不放弃CPU使用权的情况下,多次尝试获取,很可能在后面几次尝试中其他线程已经释放了锁。如果尝试指定次数后仍没有获得锁,则当前线程才会被阻塞挂起。是一种利用CPU时间换取线程阻塞与调度开销的方式。
伪共享
为了解决计算机系统内主内存和CPU之间的运行速度差,CPU和主内存之间有一级或多级高速缓冲器,成为Cache,或者CPU Cache。
当CPU访问某个变量时,首先会去看CPU Cache内是否有该变量,如果有则从中获取,如果没有就去主内存获取该变量,然后把该变量所在内存区域的一个Cache行大小的内存复制到Cache。
在Cache内部按行存储Cache行是Cache与主内存进行数据交换的单位。由于Cache行中存的是内存块,不是单个变量,可能会把多个变量存放到一个Cache行中,当多个线程同时修改一个缓存行里面多个变量时,由于同时只能有一个线程操作缓存行,所以相比每个变量放到一个缓存行,性能会有所下降。
在单线程下顺序修改一个缓存行中的变量会充分利用程序运行的局部性原则,从而加速程序运行,多线程下并发修改一个缓存行中的多个变量会竞争缓存行,从而降低程序运行性能。
如何避免伪共享
JDK1.8之前一般通过字节填充来避免该问题,创建一个变量时使用填充字段填充该变量所在缓存行。
例如:
public final static class FilledLong{
public volatile long value=0L;
public long p1,p2,p3,p4,p5,p6;
}
假设缓存行为64字节,在FieldLong中填充了6个long类型的变量,加上value本身8字节,共56字节,类对象字节码的对象头占用8字节,所以实际上一个对象占有64字节。
JDK8提供了一个sun.misc.Contented注解来解决伪共享问题。
修饰类:
@sun.misc.Contented
public final static class FilledLong{
public volatile long value = 0L;
}
修饰变量:
@sun.misc.Contented("tlr")
long threadLocalRandomSeed;