前面我们讲到了线程的安全问题,并发现了多线程操作很容易引起线程不安全的问题,由于Java中线程是由操作系统调度的,因此如果多个线程对同一个变量进行非原子性的操作就会发生线程不安全。解决这个问题我们就可以用到synchronized关键字,接下来让我们进一步学习。
1.对象头
首先让我们来了解一下对象头的概念。在Java虚拟机中,对象在内存中的存储布局可以分为3快区域:对象头、实例数据、对齐填充。其中对象头中用于存储对象的运行数据,包含对象的哈希码、GC分代年龄以及线程持有的锁等信息。
其中synchronized用的锁就是存储在对象头中,如果对象是数组类型,则虚拟机用三个自宽存储对象头,如果是非数组类型,用两个自宽存储对象头。
接下来展示对象头的存储结构:
锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit锁标志位 |
---|---|---|---|---|
无锁状态 | 对象的hashCode | 对象分代年龄 | 0 | 01 |
2.锁
了解对象头会存在锁,那么,锁又是什么呢?
- 锁用来保护代码片段, 任何时刻只能有一个线程执行被保护的代码。
- 锁可以管理试图进入被保护代码段的线程。
- 锁可以拥有一个或多个相关的条件对象。
- 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。
- 当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
- 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取变量
3.synchronized关键字
synchronized关键字是一种同步锁,使用的锁保存在对象头中,要注意,它锁的是对象而不是代码块。
(1)修饰普通方法
修饰方法时,锁的是调用此方法的对象
public class SynchronizedDemo {
public synchronized void methond() {
}
public static void main(String[] args) {
SynchronizedDemo demo = newSynchronizedDemo();
demo.method();
// 进入方法会锁 demo 指向对象中的锁;出方法会释放该锁
}
}
(2)修饰静态方法
修饰静态方法时,锁的是调用此类的所有对象
public class SynchronizedDemo {
public static synchronized void methond() {
}
public static void main(String[] args) {
SynchronizedDemo demo = newSynchronizedDemo();
SynchronizedDemo.method();
// 进入方法会锁 SynchronizedDemo类对象,即所有该类的实例对象;出方法会释放该锁
}
}
(3)修饰代码块
修饰方法时,锁的是这个代码块中Object对象
public class SynchronizedDemo {
public void methond() {
synchronized(this){
...
}
}
public static void main(String[] args) {
SynchronizedDemo demo = newSynchronizedDemo();
demo.method();
}
此代码块中锁的是调用方法的this对象
再来看下一个代码块:
public class SynchronizedDemo {
public void methond() {
synchronized (SynchronizedDemo.class) {
}
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.method();
}
}
此代码块会锁 SynchronizedDemo.class 指向对象中的锁。
以上就是synchronized关键字的三种放置的地方。
另外:锁定的对象所有同步的地方,都会进行同步互斥