前言
之前两篇文章介绍了线程的基本概念和锁的基本知识,本文主要是学习同步机制,包括使用synchronized关键字、ReentrantLock等,了解锁的种类,死锁、竞争条件等并发编程中常见的问题。
关键字synchronized
- synchronied关键字可以把任意一个非null的对象当做锁,属于独占式的悲观锁。同时属于可重入锁
- 早期的的synchronized属于重量级的锁,效率低下,因为监视器是依赖底层的操作系统Lock实现的,从6之后java对sychronized进行了优化,jdk1.6以后还引入了 大量的优化,比如自旋锁,适应性锁,锁消除,锁粗化,偏向锁,轻量级锁等。
synchronized用法
常用来保证代码的原子性,主要有三种使用方法
- 修饰实例:作用于当前的对象实例加锁,进入同步代码前获得,当前对象实例的锁。
synchronized void method() {
//业务代码
}
- 修饰静态方法: 也就是给当前类加锁,会作用于该类所有的对象实例。如果线程A调用一个实例对象的非静态synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象,因为静态synchronized方法是占用的锁是当前类的锁,而访问非静态synchronized方法占用的锁是当前实例对象的锁。
synchronized void staic method() {
//业务代码
}
- 修饰代码块: 指定加锁对象,对给定的对象/类加锁,synchronized(this object)表示进入同步前要获得给定对象的锁,synchronized(类.class)表示进入同步前要获得给定类class的锁
synchronized(this) {
//业务代码
}
synchronized实现原理
- 使用synchronized是不用我们去加锁和释放lock,unlock,是jvm已经代替去做了
- synchronized修饰代码块的时候,jvm是使用monitorenter和monitorexit两个指令实现的(监视器)
- 当修饰同步方法,jvm采用ACC_SYNCHRONIZED标记符来实现的同步, 这个标识表面了这是一个同步方法
synchronized锁住的原理
monitorenter,monitorexit,ACC_SYNCHRONIZED都是基于monitor(监视器)
所谓的Monitor其实是一种同步工具,也可以说是一种同步机制。在Java虚拟机(HotSpot)中,Monitor是由
ObjectMonitor实现的,可以叫做内部锁,或者Monitor锁。
ObjectMonitor的工作原理:
ObjectMonitor有两个队列:WaitSet、EntryList,用来保存ObjectWaiter 对象列表。
_owner,获取 Monitor 对象的线程进入 _owner 区时, _count + 1。如果线程调用了wait() 方法,此时会释放Monitor 对象, _owner 恢复为空, _count - 1。同时该等待线程进入 _WaitSet 中,等待被唤醒。
-同步是锁住的
- monitorenter,在判断拥有同步标识 ACC_SYNCHRONIZED 抢先进入此方法的线程会优先拥有 Monitor 的owner ,此时计数器+1。
- monitorexit,当执行完退出后,计数器-1,归 0 后被其他进入的线程获得
除了原子性,synchronized的可见性和有序性,可重入性怎么实现
- 可见性:线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量的时候,需要从主内存中重新读取最新的值。线程加锁后,其他线程无法获得主内存中的共享变量的值,线程解锁前必须把共享变量的最新值刷新到主内存中。
- 有序性:synchronized同步的代码块具有排他性,一次只能被一个线程拥有,所以可以保证同一个时刻,代码是单线程执行的,因为as-if-serial存在,单线程语句是能够保证最终结果是有序的,但是不保证不会进行指令重排,所以synchronized是保证有序是执行结果的有序而不是防止指令重排的有序性。
- 可重入性:synchronized是可重入锁,也就说允许一个线程二次请求自己持有的锁的临界资源,这种情况就是可重入锁,锁对象有个计数器,会记录线程获取锁的次数,当执行完对应的代码后,计数器就会减去1,只有归零就会释放锁。之所以可以重入就是因为这个计数器。
synchronized和ReentrantLock的区别
可从锁的实现、功能特点、性能维度等分析
-
锁的实现:synchronized是通过jvm实现,是java的关键字;而reentrantlock是通过jdk层面的api实现的的(一般是lock()和unlock()方法配合try/catch/finally语句实现)
-
性能:jdk1.6前synchronized性能比较差,应该都是要通过底层调用,但是1.6以后增加了适应性自旋,锁消除等,两者性能差不多。
-
功能特点:-
- ReentrantLock比synchronized增加了一些高级功能,如等待中断,可实现公平锁,可实现选择性通知;
- synchronized只能是非公平锁(内部锁),- ReentrantLock可以指定是公平还是非公平(公平锁就是先等待的线程先获得锁);
- synchronized与wait()和notify()/notifyAll()方法结合实现等待/通知机制,ReentrantLock类借助Condition接口与newCondition()方法实现。
- ReentrantLock需要手工声明来加锁和释放锁,一般跟finally配合释放锁。而synchronized不用手动释放锁