1.什么是锁?
可以参考,美团技术团队《不可不说的Java“锁”事》
多个线程在竞争一个资源的时候,需要锁机制进行确保资源的使用权。当一个线程占有资源时,其他线程需要阻塞等待(如上图所示)
- Java是怎样实现锁机制的?
线程共享的区域为:堆 + 方法区
(1)Java堆:所有线程共享的一块内存区域,此内存区域最主要的目的是:存放对象实例,几乎所有对象实例都是在这里分配内存。
(2)方法区:与Java堆一样,是各个线程共享的内存区域,他用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
针对线程共享的数据(堆中的对象)多个线程在修改同个对象的时候就会产生并发问题。
问题1:你们知道的Java锁机制哪些方法呢?(这个问题某凯不应该不会吧?)
- Synchronized锁以及 Synchronized优化锁升级
先看一段代码
public class A {
private Integer a;
public Integer getA() {
return a;
}
public void setA(String name, Integer a) {
synchronized (this) {
this.a = a;
}
}
}
对这个代码进行编译后得到字节码:
JDK1.6对Synchronized升级后,Java对象中,每个对象都会有一把锁,这把锁存在Java的"对象头"中
Java对象的组成结构(对象头 + 实例数据 + 填充字节):
其中对象中的锁存在MarkWord中,所以我们最主要看对象头的MarkWord的数据结构(非结构性):
- 32位机器的对象头MarkWord
- 64位机器上对象头MarkWord各个bit的含义
(1)无锁 阶段(锁标记位位 01,是否偏向锁:0)
无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
无锁的特点是修改操作会在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。
代码如下:
public class NoLock {
private static Object o = new Object();
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("hashcode = " + o.hashCode());
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
打印结果如下:(可以看到无锁的时候调用hashcode方法后,hashcode是存在markword中的)
PS:此处打印的markword数据是小端模式存储和打印的。 冷知识:为什么对象的分代年龄最大只能调到15?
(2)偏向锁(锁标记位位 01,是否偏向锁:1)
代码如下:
private static Object o;
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
o = new Object();
lock();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
private static void lock() {
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
单条线程在占有该对象时,锁会偏爱这个线程,此时markword会记录占有的线程ID
(3)轻量级锁(锁标记位位 10)
(3)重量级锁(锁标记位位 00)
重量级锁
升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。Java会调用操作系统的 mutex 指令(重量级),此时会涉及到操
弊端:有时候同步代码块执行的时间会远小于线程切换的时间。
锁升级过程: