synchronized 称为Java内置锁、隐式锁。因为synchronized是JVM实现的。
synchronized实现同步的基础:Java中每一个对象都可以作为锁。具体表现:
- 对于普通同步方法,锁是this对象
- 对于同步方法块,锁是synchronized括号里配置的对象
- 对于静态同步方法,锁是当前类的Class对象
package com.paddx.test.concurrent;
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
查看反编译后结果:
-
monitorenter:每个对象都和一个monitor相关联,当monitorObject._owner != null时,说明这个monitor被锁了。线程执行monitorenter指令时尝试获取monitor的所有权
1,如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
2, 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
3,如果其他线程已经占用了monitor,则该线程会先自旋,最后获得锁或者进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权; -
monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异常退出时释放锁;
Java线程是映射到os的原生线程之上的,如果要阻塞或唤醒一个线程,都需要os的来帮忙完成,这就需要从用户态切换到核心态中,因此状态转换需要耗费很多的处理器时间。
Synchronized是非公平锁。 Synchronized在线程进入ContentionList(Blocked状态)时,未获得锁线程会先尝试自旋获取锁,如果获取不到就进入ContentionList(Blocked状态),这明显对于已经进入队列(Blocked状态)的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程(Runnable中Ready状态线程)的锁资源
对象头 Mark Word
- baised_lock为1,lock为01,表示当前为偏向锁模式。
- ptr_to_lock_record 表示指向锁记录空间的指针
- ptr_to_heavyweight_monitor 指向monitorObject的指针
synronized实现原理
-
每个java对象都可以用做一个实现同步的锁, 这些锁被称为内置锁(Intrinsic Lock)或者监视器锁(Monitor Lock).
-
要实现这个目标,则每个java对象都应该与某种类型的锁数据关联。
-
在synchronized锁中,这个存储在对象头的Mark Word中的锁信息是一个指针,它指向一个monitor对象(也称为管程或监视器锁)的起始地址。这样,我们就通过对象头,将每一个对象与一个monitor关联了起来
-
图片的最左边是线程的调用栈,它引用了堆中的一个对象,该对象的对象头部分记录了该对象所使用的监视器锁地址,该监视器锁是一个monitor对象。
在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下:
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
- _owner : 当前拥有该 ObjectMonitor 的线程
- _EntryList: 当前处于BLOCKED状态等待锁的ObjectWaiter的集合
- _WaitSet: 当前处于WAITING状态的ObjectWaiter的集合
在java中,每一个等待锁的线程都会被封装成ObjectWaiter对象,当多个线程同时访问一段同步代码时,首先会被扔进 _EntryList 集合中,如果其中的某个线程获得了monitor对象,他将成为 _owner,如果在它成为 _owner之后又调用了wait方法,则他将释放获得的monitor对象,进入 _WaitSet集合中等待被唤醒。
另外,因为每一个对象都可以作为synchronized的锁,所以每一个对象都必须支持wait(),notify,notifyAll方法,使得线程能够在一个monitor对象上wait, 直到它被notify。这也就解释了这三个方法为什么定义在了Object类中——这样,所有的类都将持有这三个方法。
总结
synchronized(object1) {
object1.wait();
System.out.printLn("hello")
}
首先一个线程A尝试获取object1对象的monitor锁,
其实是将object1对象的mark word中引用的MonitorObject对象的_owner设置为当前线程。
如果这时候会有另一个线程B过来,尝试进入同步代码块,尝试获取锁失败,然后会自旋一会,
如还没有成功获取锁,则会将线程B会进入BLOCKED状态,然后线程B被封装成ObjectWaiter
放置object1对象的mark word中引用的MonitorObject对象的_EntryList里。
然后线程A会调用object1.wait();线程A会释放占用的monitor锁,
即 将object1对象的mark word中引用的MonitorObject对象的_owner设置为null。
然后线程A进入WAITING状态,且被被封装成ObjectWaiter放置object1对象的
mark word中引用的MonitorObject对象的_WaitSet里。
synchronized(object1) {
object1.signal();
}
此时线程C恰好尝试获取object1对象锁,正在自旋,恰好获得了object1对象锁,
这时object1对象的mark word中引用的MonitorObject对象的_owner为线程C。
线程C里调用object1.signal();然后会在object1对象的mark word中引用的
MonitorObject对象的_WaitSet里,随机挑选一个ObjectWaiter并唤醒该线程,
这时线程C还没有释放object1对象锁。
假设恰好唤醒线程A,这时线程A和_EntryList里的线程会竞争锁,
然后当线程A获得监视器锁,将会在直接打印"hello",说明被唤醒后的位置是在await()之后。
被唤醒的线程要从哪里恢复执行呢?当然是被挂起的地方
测试代码
public class BoundedBuffer1 {
final Object[] items = new Object[10];
int putptr, takeptr, count;
public synchronized void put(Object x) throws InterruptedException {
while (count == items.length)
wait();
items[putptr] = x;
if (++putptr == items.length)
putptr = 0;
++count;
notify();
}
public synchronized Object take() throws InterruptedException {
System.out.println("11");
while (count == 0)
wait();
System.out.println("22");
Object x = items[takeptr];
if (++takeptr == items.length)
takeptr = 0;
--count;
notify();
return x;
}
@Test
public void main() throws InterruptedException {
BoundedBuffer1 buffer = new BoundedBuffer1();
new Thread(new Runnable() {
@Override
public void run() {
try {
buffer.take();
System.out.println("buffer.take() && i = 1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread1---") .start();
System.out.println("thread1--- has started");
new Thread(new Runnable() {
@Override
public void run() {
try {
buffer.take();
System.out.println("buffer.take() && i = 2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread2---") .start();
System.out.println("thread2--- has started");
new Thread(new Runnable() {
@Override
public void run() {
try {
buffer.take();
System.out.println("buffer.take() && i = 3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread3---") .start();
System.out.println("thread3--- has started");
new Thread(new Runnable() {
@Override
public void run() {
try {
buffer.take();
System.out.println("buffer.take() && i = 4");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread4---") .start();
System.out.println("thread4--- has started");
for (int i = 10; i < 21; i++) {
buffer.put(i + "");
}
}
}
https://juejin.im/post/5b4eec7df265da0fa00a118f