保证线程同步的方法(线程通信的方法),wait/notify ,synchronized,Reentranklock,,
我所认为的认为的synchronized,锁的膨胀过程,这是一个怎样的概念:无锁状态>偏向锁>轻量级锁>重量级锁;
首先来说说为什么是这样设定(我所认为的),首先来说说对象头吧,其实我只知道冰山一角,我导入一个jar包,jol-core-0.8.jar,这个jar包可以去查看我们的对象头,今天以64位计算机来说,我们先创建一个最简单的对象,
public class L {
private String name;
}
对,没错,什么东西都没有,来看看创建一个L的对象的对象头是什么样子
L l=new L();
// l.hashCode();
// System.out.println(ClassLayout.parseClass(L.class).toPrintable());
System.out.println(ClassLayout.parseInstance(L.class).toPrintable());
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) df 03 00 20 (11011111 00000011 00000000 00100000) (536871903)
这里只是截取了对象头,采用小端,可以看出,在64为操作系统,一个对象对象头为96bit,(对象头由俩个词组成,Mark Word,Klass Pointer(类的元数据地址,方法区的类的模板信息))
其中Mark Word为64bit,Klass Pointer为32bit
都知道一个对象由对象头+实例数据+填充数据,构成,对象头主要有一个对象的锁状态,hash值,分代年龄等构成
一个对象锁状态,也是在对象头中有2个bit所保存(还有GC标记也有一位),一共是三位所保存;
在jdk1.6之前synchronized是一把重量级锁,(它只要有线程竞争就会有cup的用户态和内核态之间的转变),所以才出现了一个ReentrankLock的锁,而这个锁性能在jdk1.6之前是要比sychronized的性能好很多的,ReentrankLock详解在后续文章中;在jdk1.6之后,oracle将synchronized优化,也就是我们所说的锁的膨胀过程;我们一个状态一个状态的说:
偏向锁:适用于线程少或者线程不竞争(交替执行,一个完了,下一个才来),当一个线程经过同步代码块或同步方法时,会去加锁(假设目前无锁状态),那么如何加呢?首先没有被加锁时,这个对象或者类对象的对象头标记为无锁状态,当第一个线程,线程1来时,可以直接加锁,那么这个对象的对象头就变为偏向锁状态;即这个线程的线程id会被记录在被锁住对象的对象头中,这就是一个偏向线程1的偏向锁,即使这个线程执行完,退出了同步方法,仍然不会去释放锁(所以锁的膨胀过程时不可逆的),这个锁仍然是偏向这个线程的偏向锁,而这个线程下次在进入这个同步方法时,只需要判断当前对象的对象头的线程id与当前线程的线程id相同否,相同则直接进入,不同则判断对象头中保存的线程id对应的线程是否执行完毕(是否已经退出同步),如果执行完毕,则将对象头中的线程id改为当前线程的线程id,当前线程进入(
同样,执行完后也不会释放锁),如果上一个线程还没有执行完,则此时就是竞争的状态而非交替了,锁膨胀为轻量级锁;
那么为什么引入偏向锁呢?
同一个线程只需加锁一次,可以重复进出,不需要花费大量时间去加锁,解锁,同时这个被加锁的线程“被偏向了”,即这个线程进出快
轻量级锁:
上述说到,如果有线程竞争时,锁膨胀为轻量级锁,同样我们先来说说这个加锁过程又是怎么样的,轻量级锁,即这个被锁住的对象的对象头的俩位bit改变,(01.10.11.00)此时为轻量级锁,当第二个线程来竞争时,如果线程1执行完了,则线程2进入,加锁:虚拟机首先在这个线程的栈帧中创建一个名为锁记录(lock Record)的空间,用于储存当前线程的Mark Word 的拷贝Displaced Mark Word,也就是说相当于将对象头中的hash值,分代年龄,锁状态等拷贝一份,然后虚拟机将使用cas操作去将对象头中的Mark Word 更新为一个指向Displaced Mark Word的指针,再将Displaced Mark Word中的锁状态改为轻量级锁,如图:
如果线程1还在同步快中,线程2也不会阻塞,由轻量级锁的缘故,线程2会去进行一定次数的自旋,为什么是自旋呢?还是一个道理,让其阻塞会涉及内核的转变,会十分消耗性能,在线程竞争不激烈时(即少量线程在竞争时),让等待进入的少量线程去自旋要比让其去阻塞的性能要好很多,因为自旋相当于就是让这些等待的线程去尝试获取锁(即循环获取锁,知道成功为止),在只有少量线程竞争时,自旋好一些。当线程很多在竞争时,就膨胀为重量级锁
重量级锁:就是jdk1.6之前的锁,让所有等待的线程全部去阻塞,也就是让他们由操作系统做线程上下文的切换,由用户态转变为内核态,性能很低,但为了保证同步,只有这样,因为当很多线程竞争时,大量线程自旋会耗费大量cpu,所以让其阻塞吧
synchronized是一把非公平锁,即所有等待全部去竞争锁,没有先后之分,上述所说线程加锁,对象加锁,其实是一个概念,我们都知道一句话:被锁住的是对象,所以我们针对的是同一个对象的不同线程来加锁(如果是不同对象,就没有同步可言了);
以上所述,为笔者学习笔记,仅作参考