Java锁机制学习笔记——synchronized 和 Lock
为什么80%的码农都做不了架构师?>>>
synchronized
synchronized
关键字相信大家都不陌生了,作为java关键字,它可以帮助我们实现对方法的加锁同步。它的实现原理是基于JVM底层的,核心是控制对象的monitor的所有权来保证单一线程访问。本文主要不对其使用和原理做太多讨论,主要讲一下它的的缺点。
synchronized的缺点
非黑即白
因为synchronized
是对方法访问进行的唯一控制,同一时间内只能有一个方法能进入方法内部对资源进行操作,这样的操作包括读和写,也就是说哪怕多个线程只是试图进行线程安全的并发读操作,也只能有获得锁的那个线程能实现。
高并发性能差
性能差主要是因为两方面的原因:
- 因为上面讲的“非黑即白”的原因,导致同一时间只能有一个线程可以操作资源,导致完全无法实现并发操作。
- 当一个线程占有monitor而阻塞其余试图获取锁的线程的时候,其余线程会反复地尝试重新获得锁,这种尝试事实上可以通过一种唤醒机制来实现。
拓展性为0
因为synchronized
是由java内部机制来实现的,单纯通过java语言无法对其进行功能上的拓展,比如超时锁这样的功能无法直接添加到锁本身的特性当中。
因为有了上述的这些问题,在java1.5之后,通过引入java.util.concurrent
包,让java拥有了一个更加强大的在语法上实现的锁。
java.util.concurrent包的作者Doug Lea
Lock
Lock
是java.util.concurrent
包中定义的一个接口,它从语法层面抽象了锁的概念。它主要定义了以下几个方法:
public interface Lock {/*阻塞地获取锁,当前线程试图获取这个Lock,如果获取失败则转入挂起状态,直到重新获取锁*/void lock();/*阻塞地获取锁,不同于lock()的地方在于,如果线程在阻塞等待锁的过程中,如果调用了线程的interrupt()方法,则会抛出一个InterruptedException优先相应*/void lockInterruptibly() throws InterruptedException;/*尝试获取锁,如果成功返回true,如果失败返回false,不会进入等待获取锁的状态*/boolean tryLock();/*在指定的时间内等待获取锁,如果失败则返回false*/boolean tryLock(long time, TimeUnit unit) throws InterruptedException;/*释放锁*/void unlock();/*创建一个锁的Condition,通过condition可以对线程进行条件唤醒,类似于synchronized的notify(),但是功能更丰富*/Condition newCondition();
}
针对不同的场景,JUC(java.util.concurrent)包还提供了默认的几种实现: 下面来看看:
##ReentrantLock
ReentrantLock
是JUC中很常用的一个实现类。它主要提供以下功能:
- 基于java语法实现的锁
- 公平锁&非公平锁的实现
- 支持获取线程获取锁的状态
- 通过等待队列实现的唤醒挂起进程的功能
- 结合Condition实现的条件唤醒功能
###基于java语法实现的锁 这点毋庸置疑是革命性的,通过拓展ReentrantLock
我们可以更灵活地实现功能。
###公平锁&非公平锁 公平锁和非公平锁的主要差异来自于对新到线程是否需要排队所做的不同处理。 在公平锁中,所有的线程遵循FIFO原则,先请求获取锁的线程优先获取锁。而非公平锁则没有这样的限制,新到线程如果发现锁未被占用,立马绕过所有队列直接获取锁,如果发现锁仍被其他线程持有,才像公平锁那样加入排队(当使用lock()
方法的时候)。在使用ReentrantLock
的时候,通过传入构造参数来指定创建的锁是公平的还是非公平的。
private final Sync sync;public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
####锁的实现 通过ReentrantLock
的构造函数可以看出,公平锁与非公平锁主要在于使用了不同的Sync
实现类。而Sync是ReentrantLock
定义的一个内部类,它的继承关系是这样的: FIFO特性来自于AbstractQueuedSynchronizer
,其内部定义了一个Node
类来封装等待线程,通过链表的形式组织等待队列。
//AbstractQueuedSynchronizer.Node类的摘要
static final class Node {volatile Node prev;volatile Node next;volatile Thread thread;
}
回到ReentrantLock
,当我们调用lock()
方法的时候,本质上是调用了Sync
实现类的lock()
方法
//ReentrantLock.lock()public void lock() {sync.lock();}
公平锁FairSync
是这样实现lock()
方法的:
final void lock() {acquire(1); //进入等待线程队列,按顺序获取锁}
非公平锁NonfairSync
是这样实现lock()方法的:
final void lock() {if (compareAndSetState(0, 1)) //直接试图获取锁setExclusiveOwnerThread(Thread.currentThread());//成功获取锁else //获取锁失败则进入等待线程队列acquire(1);}
进入等待线程队列之后,通过一个state : int
来判断队列是否处于等待状态,队列初始值为0,表示没有线程在等待获取锁,则直接让当前线程获取锁。当state>1的时候表示队列仍有排队线程,则将当前线程加入队尾,并通过LockSupport.part(Thread t)
将线程挂起,直到前一个线程通过unlock()
释放锁,通过LockSupport.unpart(Thread t)`唤醒线程。
###支持获取线程获取锁的状态 当我们使用synchronized
标注方法的时候,线程本身无法判断其是否已经获取锁(因为未获取的时候进入挂起状态停止执行)。但是通过tryLock()
方法则可以试图获取锁,如果获取不成功,可以返回false给调用线程。这个功能的实现主要依赖nonfairTryAcquire ()
方法:
//ReentrantLock.nonfairTryAcquire()final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
可以发现tryLock()
所做的事情跟lock()
类似,都是首先通过CAS(compareAndSetState())方法修改state
,成功返回true,失败时tryLock()
则不会调用acquire(int i)
方法加入等待队列,而是直接返回false! 另外这里要注意一点,等待队列有长度限制,当int溢出时则会报Error。
###通过等待队列实现的唤醒挂起进程的功能 上文已经讲到了线程进入挂起状态和被唤醒时,调用的是LockSupport.park()
和LockSupport.unpark()
方法。这两个方法的底层则是使用了sun.misc.Unsafe
的park()
和unpack()
方法。Unsafe对线程控制采用了一个叫做“许可(permit)”的概念。unpark()
方法为线程提供“许可”,线程调用park()
函数则等待“许可”。这有点类似于wait()和notify()的概念,只是更加灵活。
###结合Condition实现的条件唤醒功能
Condition可以实现对线程锁更精细的控制,下面这个例子来自JavaDoc,很好地解释了Condition的功能。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[5];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock(); //获取锁try {// 如果“缓冲已满”,则等待;直到“缓冲”不是满的,才将x添加到缓冲中。while (count == items.length)notFull.await();// 将x添加到缓冲中items[putptr] = x; // 将“put统计数putptr+1”;如果“缓冲已满”,则设putptr为0。if (++putptr == items.length) putptr = 0;// 将“缓冲”数量+1++count;// 唤醒take线程,因为take线程通过notEmpty.await()等待notEmpty.signal();// 打印写入的数据System.out.println(Thread.currentThread().getName() + " put "+ (Integer)x);} finally {lock.unlock(); // 释放锁}}public Object take() throws InterruptedException {lock.lock(); //获取锁try {// 如果“缓冲为空”,则等待;直到“缓冲”不为空,才将x从缓冲中取出。while (count == 0) notEmpty.await();// 将x从缓冲中取出Object x = items[takeptr]; // 将“take统计数takeptr+1”;如果“缓冲为空”,则设takeptr为0。if (++takeptr == items.length) takeptr = 0;// 将“缓冲”数量-1--count;// 唤醒put线程,因为put线程通过notFull.await()等待notFull.signal();// 打印取出的数据System.out.println(Thread.currentThread().getName() + " take "+ (Integer)x);return x;} finally {lock.unlock(); // 释放锁}}
}public class ConditionTest2 {private static BoundedBuffer bb = new BoundedBuffer();public static void main(String[] args) {// 启动10个“写线程”,向BoundedBuffer中不断的写数据(写入0-9);// 启动10个“读线程”,从BoundedBuffer中不断的读数据。for (int i=0; i<10; i++) {new PutThread("p"+i, i).start();new TakeThread("t"+i).start();}}static class PutThread extends Thread {private int num;public PutThread(String name, int num) {super(name);this.num = num;}public void run() {try {Thread.sleep(1); // 线程休眠1msbb.put(num); // 向BoundedBuffer中写入数据} catch (InterruptedException e) {}}}static class TakeThread extends Thread {public TakeThread(String name) {super(name);}public void run() {try {Thread.sleep(10); // 线程休眠1msInteger num = (Integer)bb.take(); // 从BoundedBuffer中取出数据} catch (InterruptedException e) {}}}
}
##ReentrantReadWriteLock ReentrantReadWriteLock
提供的语法功能类似于ReentrantLock
,只是他为了读写分离添加了一些特殊的功能,使得利用这种锁控制的资源可以支持读写单独控制,特别适合那些读操作远远大于写操作的容器。
转载于:https://my.oschina.net/djzhu/blog/1094087
Java锁机制学习笔记——synchronized 和 Lock相关推荐
- 阿里P8大牛总结的Java锁机制入门笔记,堪称教科书式天花板
前言 锁机制无处不在,锁机制是实现线程同步的基础,锁机制并不是Java锁独有的,其他各种计算机语言中也有着锁机制相关的实现,数据库中也有锁的相关内容.这篇文章就是从Java入手,深入学习.理解Java ...
- JAVA 类加载机制学习笔记
JAVA 类生命周期 如上图所示,Java类的生命周期如图所示,分别为加载.验证.准备.解析.初始化.使用.卸载.其中验证.准备.解析这三个步骤统称为链接. 加载:JVM根据全限定名来获取一段二进制字 ...
- Java锁机制,synchronized和lock详解。
Java锁机制详解 1.java各种锁详解 1.1 公平锁 vs 非公平锁 公平锁:是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁.类似排队打饭,先来后到. ...
- 【并发入门】Java 并发编程学习笔记
注:该笔记主要记录自 B站 up主 遇见狂神说的个人空间_哔哩哔哩_bilibili 1.什么是 JUC Java 工具类中的 并发编程包 学习:源码 + 官方文档 业务:普通的线程代码 Thread ...
- java/android 设计模式学习笔记(1)--- 单例模式
前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...
- 转 : 深入解析Java锁机制
深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...
- java/android 设计模式学习笔记(1)---单例模式
前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...
- 面试必会系列 - 1.5 Java 锁机制
本文已收录至 github,完整图文:https://github.com/HanquanHq/MD-Notes 面试必会系列专栏:https://blog.csdn.net/sinat_424833 ...
- 《疯狂Java讲义》学习笔记 第六章 面向对象(下)
<疯狂Java讲义>学习笔记 第六章 面向对象(下) 6.1包装类 基本数据类型 包装类 byte Byte short Short int Integer long Long char ...
最新文章
- iOS点击空白收回键盘
- vector 容器 动态数组总结
- sql中union和union all的区别
- C++中string类的length()与size()方法和C语言的strlen()函数有什么区别?
- 事件EVENT,WaitForSingleObject(),WaitForMultipleObjecct()和SignalObjectAndWait() 的使用(上)
- spring cloud + spring boot + springmvc+mybatis分布式微服务云架构
- Python_Proxy代理
- C语言(二)- 函数、指针、数组
- 性能测试oracle瓶颈定位,性能测试难点之瓶颈分析
- linux python tab补全_Linux设置python自动tab自动补全
- ecshop源码教程第1季
- 航天金税3发票导入功能开发教程(一)
- 经纬度度分秒转换小数.sql[原创]
- linux新硬盘装系统,目前是windows,要全新硬盘安装linux,该怎么操作?
- 大一期末计算机考试评分标准,大学生平时成绩考核评价标准
- 篮球比赛表式计时器_篮球比赛24秒倒计时器的设计(word文档)
- java简繁体互转(附源码和字典)
- MySQL Failover搭建
- css-图片闪烁效果
- PaddleOCR 识别数据制作