java对象布局
-
组成
-
对象头
-
数据
-
填充(如果正好到8的倍数了就不用对其)
-
-
64位机器中有12个字节的对象头,对象的大小是8的倍数
什么是java的对象头?
对象的第一个部分,是所有对象都有的
虚拟机位数 | 头对象结构 | 说明 |
---|---|---|
32/64bit | Markword | 存储对象的hashcode、锁信息、分代年龄或gc标记信息 |
32/64bit | klass pointer/Class Metadata Address | 类型指针指向对象的类元数据,JVM通过这个指针确定是哪个类的实例 |
java 对象头由什么组成?
Markword + Class Metadata Address = 12 byte
当前使用的虚拟机是什么? (cmd > java -version 查看)
什么是JVM? 什么是HotSpot?
- JVM 可以理解成一种 标准/规范
- HotSpot 可以理解成一种 产品/实现
- openjdk ------ 项目 / C++ 开发, 可以这样理解openjdk就是 HotSpot 的开源项目
gc 从 survive —> old 要来回折腾16次,也就是数值达到15 因为在对象头中对应4位 0~15
Markword -------- 64bit
Class Metadata Address -----32bit 有的也写成64bit 因为有的VM开启了指令压缩(32bit) 有的没开启(64bit)
两个加在一起不就正好是96bit = 12byte了么
对象状态?
- 无状态 new出来的时候
- 偏向锁
- 轻量
- 重量锁
- gc标记
并发编程当中的锁-------ReentrantLock(同步)
为什么要用多线程呢?
因为我们要保证互斥,也就是说我们在开发业务的时候,写在Controller 里面,假如两个客户同时访问我们的网站的某一个变量i,这个时候我们就需要加锁,否则的话我们看到的结果就会不一样。
synchronized (同步) 关键字
synchronized 是一个重量级锁,需要调用OS(在1.6之前)
在jdk1.6之前 原理是一个重量锁
也就是说当我们调用这个线程方法的时候,jvm会调用一个native方法,然后会调用到操作系统函数(mutex),当下一个线程想进来的时候会先阻塞,就是操作系统中讲的那个临界区问题
注:这里的native是本地的意思,本地方法,用c++写的不是用java写的
在jdk1.7以后就完全变成了jvm来实现,不会再涉及到操作系统
ReentrantLock
自旋锁
其实就是操作系统里面讲的那个有一个临界变量,还是用代码写一下吧
volatile int status = 0;
void lock(){
//compareAndSet的意思就是当是0的时候就变成1
while(!compareAndSet(0,1)){
}
}
void unlock(){
status =0;
}
缺点:当线程拿不到锁的时候会在那里空转,也消耗cpu的资源效率太低,当当前的进程数目很多的时候就会产生这样的结果
yield + 自旋
volatile int status = 0;
void lock(){
//compareAndSet的意思就是当是0的时候就变成1
while(!compareAndSet(0,1)){
yield()
}
}
void unlock(){
status =0;
}
使用yield的方法还是虽然可以让出CPU,但是CPU调度线程是随机的啊,很可能下一个调用的还是这个线程,但是其他的线程并没有释放锁,所以还是造成了浪费
可以使用park()+自旋的方式
volatile int status = 0;
Queue parkQueue;
void lock(){
//compareAndSet的意思就是当是0的时候就变成1
while(!compareAndSet(0,1)){
park();
}
unlock();
}
void unlock(){
status =0;
lock_notify();
}
void park(){
//将当前线程加入到等待队列中
parkQueue.add(currentThread);
//释放cpu 阻塞
releaseCpu();
}
void lock_notify(){
//得到需要唤醒的线程
Thread t = parkQueue.hreader();
//唤醒等待线程
unpark();
}
ReentrantLock
- 公平
- 就是当前线程拿着锁,其余的线程来到的时候会在一个队列里面排队,等待这个t1释放锁。
- 非公平 ReentrantLock 如果你调用的是默认的方法,那么它默认就是非公平锁
- 就是当前线程拿着锁,其余的线程来到的时候会在一个队列里面排队,等待这个t1释放锁。但是如果当t1释放锁了之后(这里假设t2,t3,t4都在队列中排着队)这个时候t5到达了,那么这个时候我就执行t5
在这里强调一下,这个队列是只有在竞争情况下才有的队列,交替执行的情况下是不存在队列的
交替:t1执行完了!! t2执行
竞争:当t1执行的时候,t2来了,这个时候叫竞争
也就是说只有竞争的时候才会涉及到队列,涉及到了队列我们才会涉及到调度,那么就会用OS内核来调度
ReentrantLock的加锁的源码以及重入锁的体现
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;
}
}
//比较这个线程是不是持有锁
//同时也表示重入,也就是说当这个线程是拿到锁的线程,那么我就+1最有的值代表重入几次
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
AQS的队列的实现
注:AQS队列的每一个都是node对象,并且第一个node中的thread是null
下面是jdk的源码
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}