1. JUC开发包
java.util.concurrent
1.1 并发的源头
https://www.cnblogs.com/hill1126/p/11218050.html
原子性,可见性,有序性都是缓解CPU、内存、硬盘这三者的速度差异带来的问题。
-
原子性:多线程切换,导致线程没有按照自己的意愿正确执行;
-
可见性:数据在缓存中的更新没有及时通知其他线程;
-
有序性:编译器优化使程序的执行顺序发生变化。
缓存的可见性:在多核CPU中,每个核心都会有自己的独立缓存。CPU利用缓存,缓和了CPU和内存之间的速度差异带来的问题。当CPU的多个核心参与一个程序的运行,不同核心间进行各自的运算,把计算后的自己写入各自的缓存中而不写入内存,导致了每个线程都有不同的值,即缓存的可见性。
缓存的原子性:多线程在运行中,当一个线程对一个共享变量进行操作时,还没有执行完,就被剥夺了执行权。
2. Volatile
https://blog.csdn.net/zezezuiaiya/article/details/81456060
https://zhuanlan.zhihu.com/p/133851347
https://zhuanlan.zhihu.com/p/138819184
2.1可见性
https://www.jianshu.com/p/ef70563fe726
Java虚拟机规范中定义了一种Java内存模型(Java Memory Model)JMM,主要目的是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存或把内存取出变量这种操作。
JMM规定所有的变量都存储在主内存(Main Memory)中,每条线程都有自己的工作内存(Work Memory),线程的工作内存中保存了从主内存中拷贝的变量副本,线程对于变量的读写都在工作内存中进行,不能直接读写主内存中的变量。本线程的工作内存的变量无法被其他线程访问,只能被主线程访问。
volatile定义:当对volatile变量进行写操作后,JMM会把工作内存中的最新变量强制刷新到主内存,写操作会导致其他线程中的缓存无效。
2.2 有序性
2.2.1 什么是指令重排?
为了提高性能,在遵守as-if-serial语义(不管怎么重排序,单线程下程序的执行结果不能被改变)的情况下,编译器和处理器常常会对指令进行重排序。
- 编译器优化重排序
- 指令并行重排序
- 内存系统重排序
2.2.2 禁止指令重排
volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。
硬件层面的“内存屏障”
描述 | |
---|---|
sfence:写屏障 | 在写指令之后插入写屏障,能让写入缓存的最新数据写到主内存,保证写入的数据对其他线程可见; |
ifence:读屏障 | 在读指令之前插入读屏障,可以让高速缓存中的数据失效,重新获取主内存中的数据; |
mfence:全能屏障 | 拥有读写屏障的功能; |
lock前缀 | 一种锁,执行时会锁住内存子系统来确保执行顺序。 |
JVM层面的“内存屏障”
描述 | |
---|---|
LoadLoad屏障 | 对于这样的语句,Load1,LoadLoad,Load2,在Load2读取操作要读取的数据被访问前,Load1要读取的数据必须读取完毕; |
LoadStore屏障 | 对于这样的语句,Load1,LoadStore,Store2,在Store2写入操作被刷出前,保证Local1要读取的数据被读取完毕; |
StoreStore屏障 | 对于这样的语句,Store1,StoreStore,Store2,在Store2写入操作执行前,保证Store1的写入对所有处理器可见; |
StoreLoad屏障 | 对于这样的语句,Store1,StoreLoad,Load2,在Load2读取操作执行前,保证Store1的读取对所有处理器可见; |
JVM的实现会在volatile操作前后都加上内存屏障
2.3原子性
// Volatile与synchronized的区别
// volatile只能保证有序,可见,不能保证原子性
public class Main implements Runnable{
static volatile int x = 1;
@Override
public void run() {
for (int i = 0; i < 1000; i++){
x++;
}
}
public static void main(String[] args) throws InterruptedException {
Main main = new Main();
Thread thread = new Thread(main);
Thread thread1 = new Thread(main);
thread.start();thread1.start();
thread.join();thread1.join();
System.out.println(x);
}
}
// output
1532
2.4 字节码层面
public class ByteMain {
static volatile int x = 1;
public static void main(String[] args) {
x = 10;
}
}
# 反编译
# https://blog.csdn.net/Aussme/article/details/104623086
# 没有synchronized的字节码指令monitorenter和monitorexit,
$ javac ByteMain.java & javap -v ByteMain
MD5 checksum 8b07c198eb06c81c027641ba8599d82b
Compiled from "ByteMain.java"
public class Java.basis.volatile1.ByteMain
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
{
static volatile int x;
descriptor: I
# 这里,在synchronized中,方法级的同步是隐式的,无需通过字节码指令来控制,
# JVM可以在方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法,
# volatile用访问标志ACC_VOLATILE来区分是否遵循voaltile语义
flags: ACC_STATIC, ACC_VOLATILE
public Java.basis.volatile1.ByteMain();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
}
3. synchronized
https://blog.csdn.net/javazejian/article/details/72828483
https://blog.csdn.net/zwjyyy1203/article/details/106217887
https://zhuanlan.zhihu.com/p/150791816
当存在多个线程操作共享数据时,需要保证同一时刻只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行操作,这种方式叫做互斥锁。在Java中,关键字synchronized可以保证在同一时刻只有一个线程在操作共享数据,synchronized还有一个作用,可以保证一个线程的变化被其他线程看见,代替了volatile功能。
3.1 应用方式
-
修饰实例方法
public class InstanceMethod implements Runnable{ static int x = 0; // 当前实例方法加锁,当一个线程在访问该对象的synchronized方法时,其他线程不能访问该对象的synchronized // 但是如果是其他实例对象的话,操作数据是共享的,线程安全就没有保障,操作数据并非共享的,线程安全是有保障。 public synchronized void addX(){ x++; } @Override public void run() { for (int i = 0; i < 100000; i++) { addX(); } } public static void main(String[] args) throws InterruptedException { InstanceMethod instanceMethod = new InstanceMethod(); Thread thread = new Thread(instanceMethod); Thread thread1 = new Thread(instanceMethod); thread.start(); thread1.start(); thread.join(); thread1.join(); System.out.println(x); } } // output 200000
-
修饰静态方法
public class StaticMethod implements Runnable{ static int x = 0; // 静态方法,锁的是当前类对象,x是类成员,并发操作不会发生互斥现象。 public synchronized static void addX() { x++ ; } @Override public void run() { for (int i = 0; i < 10000; i++) { addX(); } } public static void main(String[] args) throws InterruptedException { StaticMethod staticMethod = new StaticMethod(); StaticMethod staticMethod1 = new StaticMethod(); Thread thread = new Thread(staticMethod); Thread thread1 = new Thread(staticMethod1); thread.start(); thread1.start(); thread.join(); thread1.join(); System.out.println(x); } } // output 20000
-
修饰代码块
public class CodeBlock implements Runnable{ static int x = 0; @Override public void run() { // class对象锁 synchronized (CodeBlock.class) { for (int i = 0; i < 10000; i++){ x++; } } } public static void main(String[] args) throws InterruptedException { CodeBlock codeBlock = new CodeBlock(); CodeBlock codeBlock1 = new CodeBlock(); Thread thread = new Thread(codeBlock); Thread thread1 = new Thread(codeBlock1); thread.start(); thread1.start(); thread.join(); thread1.join(); System.out.println(x); } } // output 20000
3.2 底层语义分析
Java对象头和Monitor
在JVM中,对象在内存的布局分为对象头,实例数据,对齐;
- 实例数据:存放类的属性数据信息,如果是数组的实例部分还包括数组的长度,这部分按4字节;
- 对齐:虚拟机要求对象起始地址必须是8字节的整数倍,不够填充来补齐;
- 对象头:虚拟机采用2个字节来存储对象头,其主要结构是由Mark Word和Class Metadata Address组成。
头对象结构 | 说明 |
---|---|
Mark Word | 存储对象的Hash,锁信息,分代年龄,GC标记等; |
Class Metadata Address | 类型指针指向对象的元数据,虚拟机通过这个指针来确定是那个类的实例; |
考虑JVM的空间效率,Mark Word被设计为一个非固定的数据结构,它会根据对象本身的状态复用自己的存储空间,如下:
objectMonitor(内置锁)
synchronized的对象锁,也就是重量级锁,锁标识为10,指针指向monitor的起始地址,每个对象都存在一个monitor与之关联,monitor可以与对象一起创建销毁或当线程试图获取对象的锁自动生成,monitor由ObjectMonitor实现:
// 源码在hotspot中src/share/vm/runtime/ObjectMonitor.hpp中
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
// void * volatile _owner; 表示拥有该monitor的线程
_owner = NULL;
// ObjectWaiter * volatile _WaitSet; 等待线程组成的双向循环链表
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
// ObjectWaiter * volatile _cxq ; 多线程竞争锁时的单向链表
_cxq = NULL ;
FreeNext = NULL ;
// ObjectWaiter * volatile _EntryList ; 处于等待锁状态的线程
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
class ObjectWaiter : public StackObj {
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
enum Sorted { PREPEND, APPEND, SORTED } ;
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread;
jlong _notifier_tid;
ParkEvent * _event;
volatile int _notified ;
volatile TStates TState ;
Sorted _Sorted ; // List placement disposition
bool _active ; // Contention monitoring is enabled
public:
ObjectWaiter(Thread* thread);
void wait_reenter_begin(ObjectMonitor *mon);
void wait_reenter_end(ObjectMonitor *mon);
};
ObjectMonitor有2个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表(每个等待锁的线程都会封装成ObjectWaiter对象), _owner指向持有ObjectMonitor对象的线程,当多线程同时访问一段同步代码时,首先进入 _EntryList,当线程获得对象的monitor后进入 _owner区域并把monitor中的 _owner变量设置为当前线程,monitor的计数器+1,若线程调用wait()方法,将释放当前的monitor, _owner变量为null,计时器-1,同时进入 _WaitSet集合等待被唤醒。若当前线程执行完毕也将释放monitor并复位变量的值,以便其他线程进入获取monitor。
3.3 代码块底层原理
public class Main {
public int i;
public void add(){
synchronized (this){
i++;
}
}
}
# 反编译
$ javac Main.java & javap -c Main
[1] 1468
Compiled from "Main.java"
public class Java.basis.concurrent.synchronized1.Main {
public int i;
public Java.basis.concurrent.synchronized1.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void add();
Code:
0: aload_0
1: dup
2: astore_1
# 进入同步方法
# 当执行monitorenter指令时,当前线程试图获取对象锁的monitor,当对象锁的monitor的进入计数器为0,那线程可以成功取得monitor,并将计数器设置为1,
# 如果其他线程拥有monitor的持有权,那当前线程会阻塞,直到正在执行的线程执行完毕
3: monitorenter
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
# 退出同步方法
# 线程释放monitor锁并设置计数器为0,其他线程有机会持有monitor
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
# 退出同步方法
# 异常结束时释放被执行的monitor指令
21: monitorexit
22: aload_2
23: athrow
24: return
}
3.4 方法底层原理
public class Main {
public int i;
public synchronized void add(){
i++;
}
}
# 反编译
$ javac Main.java & javap -c Main
[3] 1478
Compiled from "Main.java"
public class Java.basis.concurrent.synchronized1.Main {
public int i;
public Java.basis.concurrent.synchronized1.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
# 方法级的同步是隐式的,无需通过字节码指令来控制,
# JVM可以在方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法,
# 当方法调用时,会检查ACC_SYNCHRONIZED访问标志是否被设置了,如果被设置了,执行线程先持有monitor,然后再执行方法,方法完成再释放monitor。
# 在方法执行期间,执行线程持有monitor,其他线程都无法再获得同一个monitor,如果期间抛出异常,且无法处理异常,会在同步方法之外自动释放。
public synchronized void add();
Code:
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
}
3.5 synchronized的优化
锁的状态有四种,无锁,偏向锁,轻量级锁,重量级锁,从低到高升级,不会降级。
偏向锁
Java6后加入的新锁,锁不仅存在多线程竞争,而且总是由同一线程连续多次获得,为了减少同一线程多次取锁(CAS)引入偏向锁。
如果一个线程获得锁,那么锁就进入偏向模式,Mark Word的结构也变为偏向锁,当这个线程再次请求锁,就无需做同步操作。
对于没有锁竞争的场合,偏向锁有很好的优化效果,因为极有可能一个线程连续多次申请相同的锁。
// 源码在hotspot中src/share/vm/runtime/synchronized.cpp中
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
// 检查是否开启偏向锁
// src/share/vm/runtime/bloabls.hpp : product(bool, UseBiasedLocking, true, "Enable biased locking in JVM")
if (UseBiasedLocking) {
// 如果不处于全局安全点 ,全局安全点:在这个时间点上没有字节码正在执行
// is_at_safepoint : inline static bool is_at_safepoint() { return _state == _synchronized; }
if (!SafepointSynchronize::is_at_safepoint()) {
// 通过revoke_and_rebias获取偏向锁
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
// 在安全点,撤销偏向锁
assert(!attempt_rebias, "can not rebias toward VM thread");
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
// 不能偏向就轻量级锁
slow_enter (obj, lock, THREAD) ;
}
// 撤销锁
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
// if displaced header is null, the previous enter is recursive enter, no-op
markOop dhw = lock->displaced_header();
markOop mark ;
if (dhw == NULL) {
// Recursive stack-lock.
// Diagnostics -- Could be: stack-locked, inflating, inflated.
mark = object->mark() ;
assert (!mark->is_neutral(), "invariant") ;
if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
}
if (mark->has_monitor()) {
ObjectMonitor * m = mark->monitor() ;
assert(((oop)(m->object()))->mark() == mark, "invariant") ;
assert(m->is_entered(THREAD), "invariant") ;
}
return ;
}
mark = object->mark() ;
// If the object is stack-locked by the current thread, try to
// swing the displaced header from the box back to the mark.
if (mark == (markOop) lock) {
assert (dhw->is_neutral(), "invariant") ;
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT (fast_exit: release stacklock) ;
return;
}
}
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
revoke_and_rebias获取偏向锁流程图:
撤销偏向锁:
轻量级锁
偏向锁失败后,不会立即升级为重量级锁,成为轻量级锁。
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
// 如果当前是无锁状态
// is_neutral() : bool is_neutral() const { return (mask_bits(value(), biased_lock_mask_in_place) == unlocked_value); }
if (mark->is_neutral()) {
// 将Mark Word写入header
lock->set_displaced_header(mark);
// CAS操作,Mark Word更新为指向header的指针
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
// owner指向Mark Word
TEVENT (slow_enter: release stacklock) ;
return ;
}
} else
// mark指针是否指向当前线程的栈帧
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
// 执行同步代码块
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
lock->set_displaced_header(NULL);
return;
}
#if 0
if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
lock->set_displaced_header (NULL) ;
return ;
}
#endif
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
// 自旋,防止线程被挂起,一旦可以获取资源,就直接尝试成功,如果超出阈值,还没有获取锁,那么升级为重量级锁。
// (自旋锁默认是10次,-XX:PreBlockSpin可以修改)
lock->set_displaced_header(markOopDesc::unused_mark());
// 轻量级锁通过inflate进行膨胀升级为重量级锁
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
// 撤销轻量级锁
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit (object, lock, THREAD) ;
}
获取轻量级锁:
撤销轻量级锁:
自旋锁
轻量级锁失败后,为了避免线程在操作层面被挂起,还会进行一项称为自旋锁的优化手段。如果一直没有得到锁只能升级为重量级锁。
为什么轻量级锁还会失败?
重量级锁
一旦锁升级为重量级锁之后,就不能恢复到轻量级锁状态。当锁处于重量级锁,其他线程尝试取锁时,都会被阻塞。
3.6 synchronized的可重入性
public class ReentringDemo implements Runnable{
static int x = 0;
public void add(){
x++;
}
@Override
public void run() {
synchronized (this){
for (int i = 0; i < 100000; i++) {
x++;
add();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReentringDemo reentringDemo = new ReentringDemo();
Thread thread = new Thread(reentringDemo);
Thread thread1 = new Thread(reentringDemo);
thread.start();thread1.start();
thread.join();thread1.join();
System.out.println(x);
}
}
// output
400000
3.7线程中断
4. Locks
https://blog.csdn.net/sinat_26279177/article/details/80646301
4.1 Lock
4.1.1 Lock与synchronized的区别
- synchronized只能在一个方法内,Lock可以在不同的方法内;
- synchronized不支持公平锁,锁释放后处于等待的线程都有可能获得锁,不能指定优先级,Lock可以实现公平锁,确保等待时间最长的线程获得锁;
- synchronized等待中的线程不能被中断,Lock可以通过lockInterruptibly()方法中断等待的锁;
- synchronized不需要手动释放锁,Lock必须需要手动释放锁,没有主动释放锁,就会出现死锁的现象;
- Lock提供了tryLock()方法,只有在锁可用且没有其他线程使用时获得锁,减少了线程等待锁的阻塞时间。
4.1.2 Lock API
https://www.matools.com/api/java8
// 获得锁,如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
void lock();
// 获取锁,除非当前线程是 interrupted 。获取锁,如果可用并立即返回。
void lockInterruptibly() throws InterruptedException;
// 只有在调用时才可以获得锁。
boolean tryLock();
// 如果在给定的等待时间内是空闲的,并且当前的线程尚未得到 interrupted,则获取该锁。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 返回一个新Condition绑定到该实例Lock实例
Condition newCondition();
// 为了防止死锁,必须释放锁
lock.lock();
try{}finally{
lock.unlock();
}
// tryLock()尝试获取锁,成功返回true,失败返回false,立即返回,不会等待
// tryLock(long time, TimeUnit unit)没拿到锁会等待一定的时间。
if (lock.tryLock()) {
try{}finally{
lock.unlock();
}
} else {
}
// 线程获取锁,如果处于等待状态,则这个线程可以响应中断,即中断等待的线程。
// 也就是说,2个线程都通过lockInterruptibly()获取锁,线程A获得,但线程B没有获得,线程B调用interrupt()中断线程。
void lockInterruptibly() throws InterruptedException;
4.2 AQS
https://www.cnblogs.com/micrari/p/6937995.html
https://cloud.tencent.com/developer/article/1113761
http://ifeve.com/introduce-abstractqueuedsynchronizer/
https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/AQS%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8AAQS%E5%90%8C%E6%AD%A5%E7%BB%84%E4%BB%B6%E6%80%BB%E7%BB%93
// 抽象队列同步器
// AQS的使用方式通常是内部类继承AQS实现同步功能
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
// 提供了一个基于FIFO队列,来管理多线程的工作,在公平竞争的情况下,无法获取同步状态的线程会被封装为一个节点,置与队列尾部。
待继续研究!!!
4.3 ReentrantLock
https://blog.csdn.net/qq_29373285/article/details/85964460
公平锁和非公平锁( 当有线程竞争锁时,当前线程会首先尝试获得锁而不是在队列中进行排队等候,这对于那些已经在队列中排队的线程来说显得不公平,这也是非公平锁的由来),默认情况下为非公平锁。
// 可重入锁,实现Lock接口
// 提供了跟synchronized方法与语句使用的隐式monitor锁
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
// 默认是不公平的锁
public ReentrantLock() {
sync = new NonfairSync();
}
// Sync继承了AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
// AbstractQueuedSynchronizer继承了AOS
// AOS主要保存获取当前锁的线程对象
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
// 不公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
待继续研究!!!
4.4 ReentrantReadWriteLock
5. Atomic
原子性:多线程在运行中,当一个线程对一个共享变量进行操作时,还没有执行完,就被剥夺了执行权。
AtomicStampedReference可解决CAS发生的ABA问题。
6. Executor
6.1 ThreadPoolExecutor类
https://mp.weixin.qq.com/s?__biz=Mzg3NjU3NTkwMQ==&mid=2247505103&idx=1&sn=a041dbec689cec4f1bbc99220baa7219&source=41#wechat_redirect
6.1.1构造方法
public ThreadPoolExecutor(
// 核心线程数,最小可以同时运行的线程数量
int corePoolSize,
// 当队列中的任务达到队列的容量时,当前可以同时运行的线程数量变为最大线程数
int maximumPoolSize,
// 当线程数量超过核心线程数时,多余的空闲线程存活的最长时间
long keepAliveTime,
// keepAliveTime单位
TimeUnit unit,
// 当前线程数量达到核心线程数时,新任务会存放在队列中
BlockingQueue<Runnable> workQueue,
// 线程工厂,创建线程
ThreadFactory threadFactory,
// 拒绝策略
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
// 线程工厂,默认是Executor的defaultThreadFactory()
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
6.1.2 RejectedExecutionHandler拒绝策略
当前同时运行的线程数量达到最大限制且队列也放满了任务,会执行拒绝策略。
ThreadPoolExecutor的拒绝策略常用的是DiscardPolicy或者DiscardOldestPolicy。
// 不会被丢弃,会调用executor方法的线程来执行被拒绝的任务
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
// 抛出异常,拒绝处理
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
// 不处理,直接丢弃
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
// 丢弃最早未处理的
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
7.CAS
CAS的ABA问题:第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。
// ConcurrentHashMap的iniTable的compareAndSwapInt
// https://blog.csdn.net/aileitianshi/article/details/108844586
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// CAS比较替换,乐观锁,如果内存值和旧值相同,则将内存值替换为待修改的值
// SIZECTL(Unsafe.objectFieldOffset获得,指内存偏移值)内存值与sc旧的预期值比较 -1为待修改的值
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
// 根据操作的对象和其在内存中的offset,计算出内存中具体位置
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
// 这里判断是否交换成功,相同则交换成功,不同则交换失败
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
assert(sizeof(jbyte) == 1, "assumption.");
uintptr_t dest_addr = (uintptr_t)dest;
uintptr_t offset = dest_addr % sizeof(jint);
volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
jint cur = *dest_int;
jbyte* cur_as_bytes = (jbyte*)(&cur);
jint new_val = cur;
jbyte* new_val_as_bytes = (jbyte*)(&new_val);
// 指向new_val_as_bytes的值覆盖为exchange_value,new_val也变为exchange_value
new_val_as_bytes[offset] = exchange_value;
// 如果当前值和期望值不相同,直接返回cur_as_bytes[offset],上层方法会判断交换失败
// 相同的话做交换操作
while (cur_as_bytes[offset] == compare_value) {
// 尝试交换,调用机器指令,返回的是当前地址上的值即最新的值
// new_val内存中的值,
jint res = cmpxchg(new_val, dest_int, cur);
if (res == cur) break;
// 失败时说明有竞争, 其他线程先行改变了原来在内存里的值, 则需要继续重试
// cur改变为最新的值
// cur_as_bytes[offset]也改变为最新值
cur = res;
new_val = cur;
new_val_as_bytes[offset] = exchange_value;
}
// 有竞争时,cur_as_bytes[offset]为最新在内存中的值
return cur_as_bytes[offset];
}