并发笔记
- (类加载)将类的字节码加载到java虚拟机中(方法区中)
- 虚拟机执行main方法,在栈内分配栈帧
守护线程
- thread.setDaemon(true)设置为守护线程。
- 当其他非守护线程都运行完毕,不管守护线程是否执行完毕都会结束。
- 垃圾回收器线程就是一种守护线程。
线程状态
五状态(操作系统层面)
- 初始状态:只是语言层面创建了线程对象,还没有和操作系统线程关联。
- 就绪状态:线程已经和操作系统线程关联,可以由cpu调度。(将运行所需要的资源除了cpu都拿到了)
- 运行状态:获得时间片
- 堵塞状态:调用了阻塞api
- 终止状态:线程已经执行完毕。
六状态(更准确)(JAVAAPI层面)
JAVA通过枚举列举了6个状态
- NEW:线程刚被创建,还没有调用start方法
- RUNNABLE:调用了start方法后,涵盖了操作系统层面的可运行状态,运行状态,阻塞状态。
- terminated:线程运行结束
- Blocked,Waiting,Timed_Waiting都是JAVA API层面对阻塞状态的细分。
- blocked 阻塞:等待锁的情况,被动情况
- timed_waiting:限时等待,sleep(1000),主动
- waiting:主动
synchronized
-
synchronized实际上是用对象锁保证了临界区内代码的原子性,临界区内的代码对外不可分割,不会被线程切换所打断。
-
加在方法上的synchronized想当于锁住this对象。加在静态方法上,相当于锁住类对象。
-
实现线程安全有三种方
- 无共享变量
- 共享变量不可变
- 同步
-
成员变量可以被多个线程去访问,线程不安全。
-
局部变量在大部分情况下都是安全的。除非局部变量是个引用,都指向了堆中的一个对象。或者局部变量由于子类继承,子类新起一个新的线程去修改局部变量,导致泄漏。
-
String加个final的原因:防止子类去修改,产生一些不安全的情况,破坏某些方法的行为,导致线程不安全
对象结构
- 对象头和对象体组成
- 对象体存储一些成员变量等。
- JAVA普通对象头:包含Mark Word和Klass Word。数组对象头还多了一个数组长度。
- int 占4个字节,而Integer在32位虚拟机下占对象头8个字节和int4个字节,总共12个字节。在内存敏感的情况下用基本数据类型。
- Klass Word用于找到类对象。
- Mark Word中包含hashcode,垃圾回收的分代年龄,偏向锁和加锁状态信息(根据状态,包含的信息会有所变化)
Monitor(重量级锁)(监控器或管程)
每个java对象都可以关联一个monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9f4YEtX-1648900506517)(assets/16373961307892.jpg)]
轻量级锁
-
如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(没有存在竞争),可以使用轻量级锁来优化。
-
如果存在竞争,轻量级锁就会升级为重量级锁。
-
当存在锁竞争而导致一个线程阻塞可以通过cas自旋的方式进行优化,减少上下文的切换。
-
轻量级锁的问题:每一次锁重入都需要一次cas自旋检查。希望用锁记录去替换对象的markword。
偏向锁
-
只有第一次使用cas时将线程ID设置到对象的MarkWord头中,之后重入时发现时自己的线程id就不需要重新cas。
-
只要以后不发生竞争,则锁就偏向于该线程所拥有。(即使线程已经执行完毕释放了锁),markword里的线程id仍是该线程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BpFIHdkw-1648900506518)(assets/16374912015442.jpg)] -
偏向锁时默认开启的,但是时延迟的,不会在程序启动时立即生效,需要在程序启动后过几秒之后。
-
当存在锁竞争(升级为轻量级锁),对象执行了hashcode方法(变为普通锁)或者执行了wait/notify(变为重量级锁)方法就会导致偏向锁的偏向撤销。
-
撤销次数超过20次,则jvm认为偏向错了线程,会批量为后续的对象重偏向另外的线程。
-
撤销次数超过40次,则jvm认为就不该偏向,该类下的对象都会批量撤销偏向,并且新生成的对象也不会进行偏向。