来自《Java 并发编程之美》
只是摘抄,方便回顾,具体可以看书
进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位
线程是进程的一个执行路径,CPU分配的基本单位
一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域
程序计数器:记录该线程让出CPU时的执行地址的
线程创建与运行
Thread
class ThreadTest {
//继承Thread类并重写run方法
public static class MyThread extends Thread {
// 在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法
@Override
public void run() {
System.out.println("I am a child thread");
}
}
public static void main(String[] args) {
// 创建线程 创建完thread对象后该线程并没有被启动执行
MyThread thread = new MyThread();
// 启动线程 直到调用了start方法后才真正启动了线程
thread.start();
}
}
Runnable
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I am a child thread");
}
}).start();
FutureTask
有返回值
//创建任务类,类似Runable
public static class CallerTask implements Callable<String> {
@Override
public String call() throws Exception {
return "hello";
}
}
public static void main(String[] args) throws InterruptedException {
// 创建异步任务
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
//启动线程
new Thread(futureTask).start();
try {
//等待任务执行完毕,并返回结果
String result = futureTask.get();
System.out.println(result);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
线程获取共享变量的监视器锁
执行synchronized同步代码块时,使用该共享变量作为参数
synchronized(共享变量){
//doSomething
}
调用该共享变量的方法,并且该方法使用了synchronized修饰
synchronized void add(int a, int b){
//doSomething
}
虚假唤醒
一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()
方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒
不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待
synchronized (obj) {
while (条件不满足){
obj.wait();
}
}
notify() 函数
唤醒共享变量上调用wait
系列方法后被挂起的线程(必须在获取了共享对象的监视器锁后才可以返回,存在竞争只能获取一个)
notifyAll()
函数:唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程
join
等待某几件事情完成后才能继续往下执行
yield方法
请求让出自己的CPU使用
interrupted
interrupted()
方法 检测当前线程是否被中断,如果发现当前线程被中断,则会清除中断标志
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
public void run() {
for (; ; ) {
}
}
});
//启动线程
threadOne.start();
//设置中断标志
threadOne.interrupt();
//获取中断标志 true
System.out.println("isInterrupted:" + threadOne.isInterrupted());
//获取中断标志并重置 获取的是主线程的中断标志 false
System.out.println("isInterrupted:" + threadOne.interrupted());
//获取中断标志并重置 主线程没有中断 false
System.out.println("isInterrupted:" + Thread.interrupted());
//获取中断标志 true
System.out.println("isInterrupted:" + threadOne.isInterrupted());
threadOne.join();
}
守护线程与用户线程
用户线程未结束,正常情况 JVM 就不会退出
设置守护线程
thread.setDaemon(true);
thread.start();
ThreadLocal
ThreadLocal变量:访问这个变量的每个线程都会有这个变量的一个本地副本
Thread
类中
ThreadLocalMap threadLocals = null;
ThreadLocalMap inheritableThreadLocals = null;
每个线程的ThreadLocal变量
不是存放在ThreadLocal实例
里面,而是存放在调用线程的threadLocals变量
里面
Thread
里面的threadLocals
为何被设计为map
结构?
很明显是因为每个线程可以关联多个ThreadLocal变量
每个Thread
都有一个threadLocals
变量,它就是放k-v
的map
,类型为ThreadLocalMap
这个map
的entry
是ThreadLocal.ThreadLocalMap.Entry
具体的key
和value
类型分别是
ThreadLocal
(我们定义ThreadLocal变量
就是在定义这个key)Object
(我们定义ThreadLocal变量的值
就是在定义这个value)
实际上key是指向ThreadLocal类型变量的弱引用WeakReference<ThreadLocal<?>>
,但可以先简单理解为ThreadLocal
set
public void set(T var1) {
Thread var2 = Thread.currentThread();
// threadLocals 是否存在
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if (var3 != null) {
var3.set(this, var1);
} else {
this.createMap(var2, var1);
}
}
// 线程自己的变量 threadLocals
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
return var1.threadLocals;
}
void createMap(Thread var1, T var2) {
var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
}
get
public T get() {
Thread var1 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
// threadLocals 存在,返回对应本地变量的值
if (var2 != null) {
ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
if (var3 != null) {
Object var4 = var3.value;
return var4;
}
}
return this.setInitialValue();
}
返回本地变量的值
private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> var1) {
int var2 = var1.threadLocalHashCode & this.table.length - 1;
ThreadLocal.ThreadLocalMap.Entry var3 = this.table[var2];
return var3 != null && var3.get() == var1 ? var3 : this.getEntryAfterMiss(var1, var2, var3);
}
初始化 threadLocals
new ThreadLocal.ThreadLocalMap(Thread.currentThread(), null);
private T setInitialValue() {
Object var1 = this.initialValue(); // return null;
Thread var2 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if (var3 != null) {
var3.set(this, var1);
} else {
// new ThreadLocal.ThreadLocalMap(Thread.currentThread(), null);
this.createMap(var2, var1);
}
return var1;
}
remove
public void remove() {
ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
if (var1 != null) {
var1.remove(this);
}
}
private void remove(ThreadLocal<?> var1) {
ThreadLocal.ThreadLocalMap.Entry[] var2 = this.table;
int var3 = var2.length;
int var4 = var1.threadLocalHashCode & var3 - 1;
for(ThreadLocal.ThreadLocalMap.Entry var5 = var2[var4]; var5 != null; var5 = var2[var4 = nextIndex(var4, var3)]) {
if (var5.get() == var1) {
var5.clear();
this.expungeStaleEntry(var4);
return;
}
}
}
ThreadLocal 不支持继承
//(1)创建线程变量
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
//(2) 设置线程变量
threadLocal.set("hello world");
//(3) 启动子线程
Thread thread = new Thread(new Runnable() {
public void run() {
//(4) 子线程输出线程变量的值 获取不到在父线程设置的值
System.out.println("thread:" + threadLocal.get());
}
});
thread.start();
//(5) 主线程输出线程变量的值
System.out.println("main:" + threadLocal.get());
}
main:hello world
thread:null
同一个ThreadLocal变量
在父线程中被设置值后,在子线程中是获取不到的
在子线程thread
里面调用get
方法时当前线程为thread
线程
而这里调用set
方法设置线程变量的是main
线程,两者是不同的线程,自然子线程访问时返回null
InheritableThreadLocal
:让子线程能访问到父线程中的值
共享变量内存
Java内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,线程读写变量时操作的是自己工作内存中的变量
共享变量内存不可见:工作内存值不统一
共享变量读取到工作内存未来得及写到主存,变量又被另一个线程读取
synchronized:擦除工作内存值,从主存获取
释放刷新主存
volatile:跳过工作内存,
不保证操作原子性,会重复读取同一值
使用条件①不依赖变量当前值②不加锁(没用)
Unsafe
提供了硬件级别的原子性操作
static final Unsafe unsafe;
static final long stateOffset;
private volatile long state = 0;
static {
try {
//使用反射获取 Unsafe 的成员变量 theUnsafe
Field field = Unsafe.class.getDeclaredField("theUnsafe");
System.out.println("field = " + field.toString());
// 设置为可存取
field.setAccessible(true);
// 获取该变量的值
Object o = field.get(null);
unsafe = (Unsafe) field.get(null);
System.out.println("unsafe = " + unsafe.toString());
//获取state在TestUnSafe中的偏移量
stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.
getDeclaredField("state"));
} catch (Exception ex) {
System.out.println(ex.getLocalizedMessage());
throw new Error(ex);
}
}
public static void main(String[] args) {
TestUnSafe test = new TestUnSafe();
Boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
System.out.println(sucess);
}
Java指令重排序
public static class ReadThread extends Thread {
public void run() {
while(! Thread.currentThread().isInterrupted()){
if(ready){//(1)
System.out.println(num+num); //(2) 输出结果不一定为4
}
System.out.println("read thread....");
}
}
}
public static class Writethread extends Thread {
public void run() {
num = 2; //(3)
ready = true; //(4) 如果先执行,那么(2)可能输入 0
System.out.println("writeThread set over...");
}
}
private static int num =0;
private static boolean ready = false;
public static void main(String[] args) throws InterruptedException {
ReadThread rt = new ReadThread();
rt.start();
Writethread wt = new Writethread();
wt.start();
Thread.sleep(10);
rt.interrupt();
System.out.println("main exit");
}
伪共享
伪共享:多个变量被放入了一个缓存行中,并且多个线程同时去写入缓存行中不同的变量,由于同时只能有一个线程操作缓存行,所以相比将每个变量放到一个缓存行,性能会有所下降
为何多个变量会被放入一个缓存行呢?
缓存与内存交换数据的单位就是缓存行
CPU要访问的变量没有在缓存中找到时,根据程序运行的局部性原理,会把该变量所在内存中大小为缓存行的内存放入缓存行
如何避免伪共享
字节填充:创建一个变量时使用填充字段填充该变量所在的缓存行
// FilledLong是一个类对象,而类对象的字节码的对象头占用8字节
public final static class FilledLong {
// value变量占用8字节
public volatile long value = 0L;
// 6个long类型的变量,每个long类型变量占用8字节
public long p1, p2, p3, p4, p5, p6;
}
// 总共 (1+1+6)x 8 = 64 字节
JDK 8提供了一个sun.misc.Contended注解,用来解决伪共享问题。将上面代码修改为如下。
@sun.misc.Contended
public final static class FilledLong {
public volatile long value = 0L;
}
锁的概述
乐观锁与悲观锁
悲观锁:数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态
乐观锁:认为数据一般不会造成冲突,访问记录前不会加排他锁
Random
Random random = new Random();
// 0~5(包含0,不包含5)之间的随机数
random.nextInt(5);
public int nextInt(int var1) {
if (var1 <= 0) {
throw new IllegalArgumentException("bound must be positive");
} else {
// 根据老的种子生成新的种子
int var2 = this.next(31);
// ... 根据新的种子计算随机数
}
}
protected int next(int var1) {
AtomicLong var6 = this.seed;
long var2;
long var4;
do {
// 获取当前原子变量的种子
var2 = var6.get();
var4 = var2 * 25214903917L + 11L & 281474976710655L;
// 保证只有一个线程可以更新老的种子为新的
} while(!var6.compareAndSet(var2, var4));
return (int)(var4 >>> 48 - var1);
}
CAS 导致大量线程进行自旋测试,降低并发性能,所以ThreadLocalRandom应运而生。
ThreadLocalRandom
每个线程生成随机数时都根据自己老的种子计算新的种子,并使用新种子更新老的种子,再根据新种子计算随机数,就不会存在竞争问题
ThreadLocalRandom random = ThreadLocalRandom.current();
random.nextInt(5);
AtomicLong
// get 原子性设置 value 值为原始值 +1 之后返回原始值
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
public final long getAndAdd(long var1) {
return unsafe.getAndAddLong(this, valueOffset, var1);
}
// xxxGet 原子性设置 value 值为原始值 +1 后返回递增值
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
public final long addAndGet(long var1) {
return unsafe.getAndAddLong(this, valueOffset, var1) + var1;
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
具体例子
//(10)创建Long型原子计数器
private static final AtomicLong atomicLong = new AtomicLong();
//(11)创建数据源
private static final Integer[] arrayOne = new Integer[]{0,1,2,3,0,5,6,0,56,0};
private static final Integer[] arrayTwo = new Integer[]{10,1,2,3,0,5,6,0,56,0};
public static void main( String[] args ) throws InterruptedException
{
//(12)线程one统计数组arrayOne中0的个数
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
int size = arrayOne.length;
for (Integer integer : arrayOne) {
if (integer == 0) {
atomicLong.incrementAndGet();
}
}
}
});
//(13)线程two统计数组arrayTwo中0的个数
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
int size = arrayTwo.length;
for (Integer integer : arrayTwo) {
if (integer == 0) {
atomicLong.incrementAndGet();
}
}
}
});
//(14)启动子线程
threadOne.start();
threadTwo.start();
//(15)等待线程执行完毕
threadOne.join();
threadTwo.join();
System.out.println("count 0:" + atomicLong.get());
}
LongAdder
AtomicLong的性能瓶颈:过多线程同时去竞争一个变量的更新
LongAdder:把一个变量分解为多个变量,让同样多的线程去竞争多个资源
多个线程在争夺同一个Cell原子变量时如果失败了,它并不是在当前Cell变量上一直自旋CAS重试,而是尝试在其他Cell的变量上进行CAS尝试,这个改变增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base返回的。
LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。
由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。
(1)LongAdder的结构是怎样的?
(2)当前线程应该访问Cell数组里面的哪一个Cell元素?
(3)如何初始化Cell数组?
(4)Cell数组如何扩容?
(5)线程访问分配的Cell元素有冲突后如何处理?
(6)如何保证线程操作被分配的Cell元素的原子性?
transient关键字: 将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化
// Striped64 类中
transient volatile Striped64.Cell[] cells;
transient volatile long base;
transient volatile int cellsBusy;
LongAdder
类中
public void add(long var1) {
Cell[] var3;
long var4;
// 一:cells 不为空 或 二:cells为空,执行 CAS 操作
if ((var3 = this.cells) != null || !this.casBase(var4 = this.base, var4 + var1)) {
boolean var10 = true;
long var6;
int var8;
Cell var9;
// cells 为空,之前执行过了 CAS 执行 longAccumulate
// cells 不为空,cells 长度为大于等于0 ,访问Cell元素访问不存在| 存在,CAS更新Cell的value失败 执行 longAccumulate
if (var3 == null || (var8 = var3.length - 1) < 0 || (var9 = var3[getProbe() & var8]) == null || !(var10 = var9.cas(var6 = var9.value, var6 + var1))) {
this.longAccumulate(var1, (LongBinaryOperator)null, var10);
}
}
}
// Cell
final boolean cas(long var1, long var3) {
return UNSAFE.compareAndSwapLong(this, valueOffset, var1, var3);
}
Striped64
类中的 longAccumulate
方法
final void longAccumulate(long var1, LongBinaryOperator var3, boolean var4) {
int var5;
if ((var5 = getProbe()) == 0) {
// UNSAFE.putLong(var4, SEED, var2); UNSAFE.putInt(var4, PROBE, var1);
ThreadLocalRandom.current();
// return UNSAFE.getInt(Thread.currentThread(), PROBE);
var5 = getProbe();
var4 = true;
}
boolean var6 = false;
while(true) {
Striped64.Cell[] var7;
int var9;
long var10;
if ((var7 = this.cells) != null && (var9 = var7.length) > 0) {
Striped64.Cell var8;
// 计算要访问的 Cell 元素下标
if ((var8 = var7[var9 - 1 & var5]) == null) {
if (this.cellsBusy == 0) {
Striped64.Cell var32 = new Striped64.Cell(var1);
if (this.cellsBusy == 0 && this.casCellsBusy()) {
boolean var33 = false;
try {
Striped64.Cell[] var14;
int var15;
int var16;
if ((var14 = this.cells) != null && (var15 = var14.length) > 0 && var14[var16 = var15 - 1 & var5] == null) {
var14[var16] = var32;
var33 = true;
}
} finally {
this.cellsBusy = 0;
}
if (var33) {
break;
}
continue;
}
}
var6 = false;
} else if (!var4) {
var4 = true;
} else {
if (var8.cas(var10 = var8.value, var3 == null ? var10 + var1 : var3.applyAsLong(var10, var1))) {
break;
}
// 如果 Cell 数组元素个数小于 CPU 个数 (每个CPU都运行一个线程才会使多线程的效果最佳,即 Cell 数组元素个数与CPU个数一致)
if (var9 < NCPU && this.cells == var7) {
if (!var6) {
var6 = true;
} else if (this.cellsBusy == 0 && this.casCellsBusy()) { // 扩容
try {
if (this.cells == var7) {
Striped64.Cell[] var34 = new Striped64.Cell[var9 << 1];
for(int var35 = 0; var35 < var9; ++var35) {
var34[var35] = var7[var35];
}
this.cells = var34;
}
} finally {
this.cellsBusy = 0;
}
var6 = false;
continue;
}
} else {
var6 = false;
}
}
// 重新计算hash
var5 = advanceProbe(var5);
} else if (this.cellsBusy == 0 && this.cells == var7 && this.casCellsBusy()) { // 初始化 Cell 数组
boolean var12 = false;
try {
if (this.cells == var7) {
Striped64.Cell[] var13 = new Striped64.Cell[2];
// var5:访问 Cell 数组的那个位置
var13[var5 & 1] = new Striped64.Cell(var1);
this.cells = var13;
var12 = true;
}
} finally {
this.cellsBusy = 0;
}
if (var12) {
break;
}
} else if (this.casBase(var10 = this.base, var3 == null ? var10 + var1 : var3.applyAsLong(var10, var1))) {
break;
}
}
}
LongAccumulator
LongAdder
类是 LongAccumulator
类的一个特例
private final LongBinaryOperator function;
public void accumulate(long var1) {
Cell[] var3;
long var4;
long var8;
// 可以让用户自定义类加规则
if ((var3 = this.cells) != null || (var8 = this.function.applyAsLong(var4 = this.base, var1)) != var4 && !this.casBase(var4, var8)) {
boolean var12 = true;
long var6;
int var10;
Cell var11;
if (var3 == null || (var10 = var3.length - 1) < 0 || (var11 = var3[getProbe() & var10]) == null || !(var12 = (var8 = this.function.applyAsLong(var6 = var11.value, var1)) == var6 || var11.cas(var6, var8))) {
this.longAccumulate(var1, this.function, var12);
}
}
}
@FunctionalInterface
public interface LongBinaryOperator {
long applyAsLong(long var1, long var3);
}
区别
// LongAdder
!this.casBase(var4 = this.base, var4 + var1);
// LongAccumulator
(var8 = this.function.applyAsLong(var4 = this.base, var1)) != var4 && !this.casBase(var4, var8)