多线程
线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域 ThreadLocal的数据结构
//threadLocalHashCode的值是HASH_INCREMENT = 0x61c88647每次+1,取0x61c88647是因为这个值是黄金分割点
//每个线程有唯一的一个ThreadLocalMap,每个ThreadLocalMap中可以存放多个ThreadLocal对象
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; //table是一个弱引用对象数组
//INITIAL_CAPACITY=16
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//有hash证明这是个采用哈希散列方式进行存储
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);//设置扩容因子为len * 2 / 3
}
cleanSomeSlots:启发式清理,试探的扫描一些单元格,寻找过期元素,也就是被垃圾回收的元素 expungeStaleEntry:探测式清理,是以当前遇到的 GC 元素开始,向后不断的清理。直到遇到 null 为止,才停止 rehash 计算
在set中会对当前线程ThreadLocalMap进行处理 在get中会获取当前线程ThreadLocalMap中存储的值,没有就返回默认值
若堆中ThreadLocal对象会被gc回收,Entry的key为null,但是value不为null,且value也是强引用(连线6),所以Entry仍旧不能回收,只能释放ThreadLocal的内存,仍旧可能导致内存泄漏。在没有自动清理陈旧Entry的前提下,即使Entry使用弱引用,仍可能出现内存泄漏。 在threadlocal进行set、get、remove时都会进行内存回收操作,这样就避免了内存泄漏
线程同步,使用synchroinized或者ReentrantLock
- 实现Runnable接口,支持多继承
- 实现Callable接口,支持多继承,支持获取线程返回值
- 继承Thread类
两者都不会释放锁,yield只会使同优先级或者更高优先级的线程得到执行机会
阻塞态:抛出InterruptedException,同时调用interrupte方法,此时isInterrupted返回true,然后被重置为false 非阻塞态:会调用interrupted方法
thread.stop()调用之后,创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性
- 原子性(Atomicity):单个或多个操作是要么全部执行,要么都不执行
- Lock:保证同时只有一个线程能拿到锁,并执行申请锁和释放锁的代码
- synchronized:对线程加独占锁,被它修饰的类/方法/变量只允许一个线程访问
- 可见性(Visibility):当一个线程修改了共享变量的值,其他线程能够立即得知这个修改
- volatile:保证新值能立即同步到主内存,且每次使用前立即从主内存刷新;
- synchronized:在释放锁之前会将工作内存新值更新到主存中
- 有序性(Ordering):程序代码按照指令顺序执行
- volatile: 本身就包含了禁止指令重排序的语义
- synchronized:保证一个变量在同一个时刻只允许一条线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行地进入
- 使用this修饰代码块,同步的同一个对象对代码块的多线程访问
- 修饰非静态方法,同步的是同一个对象对这个方法的多线程访问
- 修饰静态方法,同步的是这个类所有对象对这个方法的多线程访问
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存
- Lock前缀的指令会引起处理器缓存写回内存;
- 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
- 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。
CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。 CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作
- 循环时间长开销很大。
- 只能保证一个共享变量的原子操作。
- ABA问题。使用AtomicStampedReference
- RUNNING:运行状态,接受新的任务并且处理队列中的任务。
- SHUTDOWN:关闭状态(调用了shutdown方法)。不接受新任务,,但是要处理队列中的任务。
- STOP:停止状态(调用了shutdownNow方法)。不接受新任务,也不处理队列中的任务,并且要中断正在处理的任务。
- TIDYING:所有的任务都已终止了,workerCount为0,线程池进入该状态后会调 terminated() 方法进入TERMINATED 状态。
- TERMINATED:终止状态,terminated() 方法调用结束后的状态。
tryAcquire方法它是不允许重入的,而ReentrantLock是允许重入的。对于线程来说,如果线程正在执行是不允许其它锁重入进来的。 线程只需要两个状态,一个是独占锁,表明正在执行任务;一个是不加锁,表明是空闲状态。
锁的处理分为了两部分,一部分为如何加解锁,另一部分为把锁分配给谁,AQS就是为了解决把锁分配给谁的问题 AQS:AbstractQueuedSynchronizer,队列同步器
- ReentrantReadWriteLock 读写锁,读锁是共享锁、写锁是独占锁
- ReentrantLock 可重入锁,其内部类Sync继承AQS
- 当锁被线程持有,AQS询问是否加锁成功时,Sync如果发现申请的线程与持有锁的线程是同一个,它将通过CAS更新state状态再次分配锁,并回复加锁成功。也就实现了重入。
- 是否公平体现在,在向AQS申请分配锁时,有一次询问是否加锁成功的机会,在此时是否忽略CLH队列中等待的线程,就代表了是否给予插队的机会
- CountDownLatch 闭锁
- Semaphore 信号量锁,主要用于控制流量 ,new Semaphore(2, false);
- 当申请锁,即调用了与acquire()类似语义的方法时,AQS将询问子类是否上锁成功,成功则继续运行。否则,AQS将以Node为粒度,记录这个申请锁的请求,将其插入自身维护的CLH队里中并挂起这个线程。
- 在CLH队列中,只有最靠近头节点的未取消申请锁的节点,才有资格申请锁。
- 当线程被唤醒时,会尝试获取锁,如果获取不到继续挂起;获取得到则继续运行。
- 当一个线程释放锁,即调用release()类似语义的方法时,AQS将询问子类是否解锁成功,有锁可以分配,如果有,AQS从CLH队列中主动唤起合适的线程
- 如果需要等待条件满足再去申请锁,即调用了wait()类似语义的方法时,在AQS中表现为,以Node为粒度,维护一个单向等待条件队列,把Node所代表的线程挂起。
- 当条件满足时,即调用了signal()类似语义的方法时,唤醒等待条件队列最前面的未取消等待的Node
- 子类可以维护AQS的state属性来记录加解锁状态,AQS也提供了CAS的方法compareAndSetState()抢占更新state。
CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用
- 可重入锁:可重入锁又名递归锁,直指同一个线程在外层方法获得锁之后,在进入内层方法时,会自动获得锁。可重入锁的好处之一就是在一定程度上避免死锁
- 自旋锁:自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态,但是它需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景
- 锁消除:是指对于被检测出不可能存在竞争的共享数据的锁进行消除。
- 锁粗化:如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操作序列的外部
- 轻量级锁:轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销
- 偏向锁
- 轻量级锁
- 重量级锁