synchronized是java提供线程间同步的重要机制
保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果
java内存模型:
先通过一个生产者消费者例子来了解如何使用synchronized
package com.example.demo;
public class SyncTest {
static Object obj = new Object();
static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread c1 = new Thread(new Consumer());
Thread p1 = new Thread(new Producer());
c1.start();
Thread.sleep(1000);
p1.start();
c1.join();
p1.join();
}
static class Consumer implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("Consumer executing...");
while (!flag) {
System.out.println("no product, waiting");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Consumer waiting end....");
}
flag = false;
System.out.println("consuming.....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exit consumer.....");
}
}
}
static class Producer implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("Producer executing......");
try {
System.out.println("Producing.....");
Thread.sleep(2000);
flag = true;
obj.notify();
Thread.sleep(2000);
System.out.println("Producer exit......");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
当然上面是代码块的方式,另一种方式是用在方法上。
- synchronized锁的是谁?
- 锁是如何实现的?
- wait()和notify()发生了什么?
synchronized锁的是谁?
synchronized有两种使用方式,分别来进行讨论
- 代码块
我们知道,使用代码块的时候synchronized后面要传入一个对象(Object)或者类(obj.class),所以结论就是传的是谁锁的就是谁。 - 方法
方法又分为静态方法和非静态方法
- 静态方法:锁的是方法所在的类
- 非静态方法:锁的是调用该方法的对象
锁是如何实现的?
关键词:Monitor对象,monitorenter和monitorexit指令,markword
monitor:每个对象都是一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权。
Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为 “重量级锁”。
再具体一点,Monitor是由ObjectMonitor实现的,其源码是用C++语言编写的,
// src/share/vm/runtime/objectMonitor.hpp
ObjectMonitor() {
_header = NULL;
_count = 0; //锁的计数器,获取锁时count数值加1,释放锁时count值减1
_waiters = 0, //等待线程数
_recursions = 0; // 线程重入次数
_object = NULL; // 存储Monitor对象
_owner = NULL; // 持有当前线程的owner
_WaitSet = NULL; // wait状态的线程列表
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 阻塞在EntryList上的单向线程列表
FreeNext = NULL ;
_EntryList = NULL ; // 处于等待锁状态block状态的线程列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
获取Monitor和释放Monitor的流程如下:
当多个线程同时访问同步代码块时,首先会找到对象或类对应的Monitor对象,进入到EntryList中,然后通过CAS的方式尝试将Monitor中的owner字段设置为当前线程,如果成功count加1;如果之前的owner的值就是指向当前线程的,owner和recursions都需要加1。如果CAS尝试获取锁失败,则进入到EntryList中。
当获取锁的线程调用wait()方法(只有获取到锁才能用wait方法),则会将owner设置为null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒。
当前线程执行完同步代码块时,则会释放锁,count减1,recursions减1。当recursions的值为0时,说明线程已经释放了锁。
wait(), notify()
Jdk1.6为什么要对synchronized进行优化?
因为Java虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的,而Monitor是依靠底层操作系统的Mutex Lock来实现的,操作系统实现线程之间的切换需要从用户态转换到内核态,这个切换成本比较高,对性能影响较大。