synchronized关键字和锁
示例代码
public class MyThreadTest2 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
MyClass myClass2 = new MyClass();
Thread t1 = new Thread1(myClass);
//Thread t2 = new Thread2(myClass);
Thread t2 = new Thread2(myClass2);
t1.start();
try {
Thread.sleep(700);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyClass {
public synchronized void hello () {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
public synchronized void world() {
System.out.println("world");
}
}
class Thread1 extends Thread {
private MyClass myClass;
public Thread1(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
myClass.hello();
}
}
class Thread2 extends Thread {
private MyClass myClass;
public Thread2(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
myClass.world();
}
}
结论
透过字节码理解synchronized关键字
修饰代码块
public class MyTest1 {
private Object object = new Object();
public void method() {
synchronized (object) {
System.out.println("hello world");
throw new RuntimeException();
}
}
public void method2() {
synchronized (object) {
System.out.println("welcome");
}
}
}
method方法为一定抛异常的方法
method2方法为一个普通方法
通过javap -v MyTest1.class查看字节码文件
修饰普通方法和静态方法
public class MyTest2 {
public synchronized void method() {
System.out.println("hello world");
}
}
public class MyTest3 {
public static synchronized void method() {
System.out.println("hello world");
}
}
monitor
- 总结一下:同步锁在这种实现方式当中,因为Monitor是依赖于底层的操作系统实现,这样就存在用户态与内核态之间的切换,所以会增加性能开销。
- 自旋(Spin)
互斥锁
普通锁(PTHREAD_MUTEX_TIMED_NP)
- 当一个线程加锁以后,其余请求锁的线程将会形成一个等待队列,并且在解锁后按照优先级获取到锁。这种策略可以确保资源分配的公平性。(缺省值)
嵌套锁(PTHREAD_MUTEX_RECURSIVE_NP)
- 允许一个线程对同一个锁成功获取多次,并通过unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新进行竞争。
检错锁(PTHREAD_MUTEX_ERRORCHECK_NP)
- 如果一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同,这样就保证了当不允许多次加锁时不会出现最简单情况下的死锁。
适应锁(PTHREAD_MUTEX_ADAPTIVE_NP)
- 适应锁,动作最简单的锁类型,仅仅等待解锁后重新竞争。
synchronized关键字的优化
在JDK 1.5之前,我们若想实现线程同步,只能通过synchronized关键字这一种方式来达成;底层,Java也是通过synchronized关键字来做到数据的原子性维护的;synchronized关键字是JVM实现的一种内置锁,从底层角度来说,这种锁的获取与释放都是由JVM帮助我们隐式实现的。
从JDK 1.5开始,并发包引入了Lock锁,Lock同步锁是基于Java来实现的,因此锁的获取与释放都是通过Java代码来实现与控制的;然而,synchronized是基于底层操作系统的Mutex Lock来实现的,每次对锁的获取与释放动作都会带来用户态与内核态之间的切换,这种切换会极大地增加系统的负担;在并发量较高时,也就是说锁的竞争比较激烈时,synchronized锁在性能上的表现就非常差。
从JDK 1.6开始,synchronized锁的实现发生了很大的变化;JVM引入了相应的优化手段来提升synchronized锁的性能,这种提升涉及到偏向锁、轻量级锁及重量级锁等,从而减少锁的竞争所带来的用户态与内核态之间的切换;这种锁的优化实际上是通过Java对象头中的一些标志位来去实现的;对于锁的访问与改变,实际上都与Java对象头息息相关。
从JDK 1.6开始,对象实例在堆当中会被划分为三个组成部分:对象头、实例数据与对齐填充。
对象头主要也是由3块内容来构成:
Mark Word
指向类的指针
数组长度
其中Mark Word(它记录了对象、锁及垃圾回收相关的信息,在64位的JVM中,其长度也是64bit)的位信息包括了如下组成部分:
- 无锁标记
- 偏向锁标记
- 轻量级锁标记
- 重量级锁标记
- GC标记
- 对于锁的演化来说,它会经历如下阶段:
偏向锁
如果是另外一个线程访问这个synchronized方法,那么实际情况会如何呢?
偏向锁会被取消掉。
轻量级锁
自旋锁
重量级锁
编译器对于锁的优化措施
锁消除技术
public class MyTest4 {
// private Object object = new Object();
public void method() {
Object object = new Object();
synchronized (object) {
System.out.println("hello world");
}
}
}
锁粗化
public class MyTest5 {
private Object object = new Object();
public void method() {
synchronized (object) {
System.out.println("hello world");
}
synchronized (object) {
System.out.println("welcome");
}
synchronized (object) {
System.out.println("person");
}
}
}
死锁,活锁和饿死
死锁:线程1等待线程2互斥持有的资源,而线程2也在等待线程1互斥持有的资源,两个线程都无法继续执行
活锁:线程持续重试一个总是失败的操作,导致无法继续执行
饿死:线程一直被调度器延迟访问其赖以执行的资源,也许是调度器先于低优先级的线程而执行高优先级的线程,同时总是会有一个高优先级的线程可以执行,饿死也叫做无限延迟