JDK源码剖析笔记
最近好久没有更新简书、实属非本人意愿;纯粹因为年关将近、工作繁忙;导致没有时间去学习新的一些知识分享给各位、如有断更;在这里跟各位抱歉,Sorry;最近抽空又去复习了一些Java的并发相关的书籍;记录了一下笔记跟各位分享;
- 线程的优雅关闭 stop、destory函数
- 在线程当中Thread方法中提供了stop(停止)、destory(销毁)等停止线程的方法、但是官方并不建议使用;因为当一个线程处于运行状态强行中断线程、线程中的资源等可能不能正常关闭;因此需要涉及到线程的通信机制、让主线程通知其退出线程;
- t.isInterrupted()与Thread.interrupted()的区别
- t.isInterrupted()与Thread.interrupted()的区别因为t.interrupted()相当于给线程发送了一个唤醒的信号,所以如果线程此时恰好处于WAITING或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒。而如果线程此时并没有被阻塞,则线程什么都不会做。但在后续,线程可以判断自己是否收到过其他线程发来的中断信号,然后做一些对应的处理,这也是本节要讲的两个函数。
- synchronized关键字
1.对于非静态成员函数,锁其实是加在对象a上面的;对于静态成员函数,锁是加在A.class上面的。当然,class本身也是对象。这间接回答了关于synchronized 的常见问题:一个静态成员函数和一个非静态成员函数,都加了synchronized关键字,分别被两个线程调用,它们是否互斥?很显然,因为是两把不同的锁,所以不会互斥。
- 锁的本质是什么?
从程序角度来看,锁其实就是一个“对象”,这个对象要完成以下几件事情:
(1)这个对象内部得有一个标志位(state变量),记录自己有没有被某个线程占用(也就是记录当前有没有游客已经进入了房子)。最简单的情况是这个state有0、1两个取值,0表示没有线程占用这个锁,1表示有某个线程占用了这个锁。
(2)如果这个对象被某个线程占用,它得记录这个线程的thread ID,知道自己是被哪个线程占用了(也就是记录现在是谁在房子里面)。
(3)这个对象还得维护一个thread id list,记录其他所有阻塞的、等待拿这个锁的线程(也就是记录所有在外边等待的游客)。在当前线程释放锁之后(也就是把state从1改回0),从这个thread idlist里面取一个线程唤醒
5.synchronized实现原理
在Java的对象头里。在对象头里,有一块数据叫Mark Word。在64位机器上,Mark Word是8字节(64位)的,这64位中有2个重要字段:锁标志位和占用该锁的thread ID
- wait()的执行过程
在wait()的内部,会先释放锁obj1,然后进入阻塞状态,之后,它被另外一个线程用notify()唤醒,去重新拿锁!其次,wait()调用完成后,执行后面的业务逻辑代码,然后退出synchronized同步块,再次释放锁。
- volatile关键字 内存可见性
对于一个long型变量的赋值和取值操作而言,在多线程场景下,线程A调用set(100),线程B调用get(),在某些场景下,返回值可能不是100;这是因为JVM的规范并没有要求64位的long或者double的写入是原子的。在32位的机器上,一个64位变量的写入可能被拆分成两个32位的写操作来执行;
- volatile实现原理
(1)在volatile写操作的前面插入一个StoreStore屏障。保证volatile写操作不会和之前的写操作重排序。>>(2)在volatile写操作的后面插入一个StoreLoad屏障。保证volatile写操作不会和之后的读操作重排序
(3)在volatile读操作的后面插入一个LoadLoad屏障+LoadStore屏障。保证volatile读操作不会和之后的读操作、写操作重排序
- happen-before规则总结
(1)单线程中的每个操作,happen-before于该线程中任意后续操作。
(2)对volatile变量的写,happen-before于后续对这个变量的读。
(3)对synchronized的解锁,happen-before于后续对这个锁的加锁。
(4)对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的读。
- 悲观锁与乐观锁
AtomicInteger的实现就是典型的乐观锁
AtomicInteger的实现就用的是“自旋”策略,如果拿不到锁,就会一直重试
- 读写锁实现的基本原理
ReadLock和WriteLock是两把锁,实际上它只是同一把锁的两个视图而已。什么叫两个视图呢?可以理解为是一把锁,线程分成两类:读线程和写线程。读线程和读线程之间不互斥(可以同时拿到这把锁),读线程和写线程互斥,写线程和写线程也互斥。
- readerLock和writerLock
- readerLock和writerLock实际共用同一个sync对象。sync对象同互斥锁一样,分为非公平和公平两种策略,并继承自AQS。
- 同互斥锁一样,读写锁也是用state变量来表示锁状态的。只是state变量在这里的含义和互斥锁完全不同。
- 也就是把state 变量拆成两半,低16位,用来记录写锁。但同一时间既然只能有一个线程写,为什么还需要16位呢?这是因为一个写线程可能多次重入。例如,低16位的值等于5,表示一个写线程重入了5次。
- concurrentHashMap原理
concurrentyLevel=9,在构造函数里面会找到比9大且距9最近的2的整数次方,也就是ssize=16。对应segmentShift、segmentMask两个变量,是为了方便计算hash使用的。初始的时候,如果不指定任何参数,就会使用默认值,代码如下所示。可以看到,默认的Segment数组大小是16。
JDK 8的实现有很大变化,首先是没有了分段锁,所有数据都放在一个大的HashMap中;其次是引入了红黑树,
- ForkJoinWorkThread状态与个数分析
(1)空闲状态(放在Treiber Stack里面)。
(2)活跃状态(正在执行某个ForkJoinTask,未阻塞)。
(3)阻塞状态(正在执行某个ForkJoinTask,但阻塞了,于是调用join,等待另外一个任务的结果返回)。
- 顺序锁eqLock
(1)读线程在读取共享数据之前先读取sequence number,在读取数据之后再读一次sequencenumber,如果两次的值不同,说明在此期间有其他线程修改了数据,此次读取数据无效,重新读取;
(2)写线程,在写入数据之前,累加一次sequence number,在写入数据之后,再累加一次sequence number。