前言
在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。在JDK1.5之前都是使用synchronized关键字保证同步的。它可以把任意一个非NULL的对象当作锁。
一、synchronized是什么?
synchronized是Java中的关键字,是一种同步锁。synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。同时它的底层又可以分为四种状态(偏向锁 无锁 轻量级锁 重量级锁)。
锁状态 是否是偏向锁 锁标志
无锁不可偏向 01
无锁可偏向 1 01
轻量级锁 00
重量级锁 10注意事项:
修改偏向锁的延迟时间的参数:-XX:BiasedLockingStartupDelay=0
关闭指针压缩: -XX:-UseCompressedOops
1.无锁状态
//在测试之前,先引入包jol--core-0.9.jar,通过ClassLayout.parseInstance(obj)查看对象所处的锁的状态
import org.openjdk.jol.info.ClassLayout
没有开启偏向锁的情况下:一个对象没有被作为锁对象,处于无锁状态
public class Test02 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
String str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//01 无锁状态
}
}
运行结果示意:
现在a对象处于无锁不可偏向状态
com.apesource.demo04.synchronized锁的描述.A object internals:
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) 43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
2.轻量级锁
一个对象被一个线程获取作为锁对象,处于轻量级锁状态
代码测试:
public class Test02 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
String str = ClassLayout.parseInstance(a).toPrintable();
new Thread(){
public void run() {
//把a作为锁对象
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//00 轻量级锁
}
}
运行结果示意
此时对象a被匿名对象获取成为锁对象,处于轻量级锁,没有偏向,后两个字节表示对象头中锁添加的id
3.重量级锁状态
在 上面代码的基础上新增此代码,加入访问锁,此时a对象被第一个线程持有,所以线二应该处于重量级锁状态
new Thread(){
public void run() {
//把a作为锁对象
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
//这个时候第一个线程正在使用a这个锁,第二个线程也要去获取a这个锁
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//10 重量级锁
运行代码展示:
偏向锁
:
大部分情况下,锁不仅仅不存在多线程竞争,而是总是由同一个线程多次获得,
为了让线程获取锁的代价更低就引入了偏向锁的概念。
怎么理解偏向锁呢?当一个线程访问加了同步锁的代码块时,
会在对象头中存储当前线程的 ID,
后续这个线程进入和退出这段加了同步锁的代码块时,
不需要再次加锁和释放锁。
而是直接比较对象头里面是否存储了指向当前线程的偏向锁。
如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了。
无锁可偏向
public static void main(String[] args) throws InterruptedException {
A a = new A();
String str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//101 无锁可偏向
new Thread() {
public void run() {
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
* 显示的让当前线程不结束
* 如果结束了,下一个线程的id和刚才这个线程的id是一样的。
*/
try {
Thread.sleep(100000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
Thread.sleep(100);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//101 无锁可偏向
Thread.sleep(1000);//保证上一个线程已经把任务执行完毕了
new Thread() {
public void run() {
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(100);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//
}
显示的让当前线程不结束
如果结束了,下一个线程的id和刚才这个线程的id是一样的。
Thread.sleep(100000000);
会导致下一个线程到来时依旧是无锁可偏向状态,可能第二个线程id和上面第一个线程的id相同
黄色框内是显示对象头中存储当前线程的 ID,
只有当上一个锁没有执行完毕(已经出synchronized代码块)下一个锁到来时才会升级为轻量级线程
总结
Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。锁的状态总共有四种,级别从低到高依次是:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以升级但不能降级。 偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的性能。现在经过不断的优化synchronized可能会进行更加高效的优化,对synchronized的学习也是不能忽视的。