网站首页 > 精选教程 正文
本文目录:
1 基础
1.1 可重入锁
可重入锁表示的是,如果一个线程在未释放已获得锁的情况下再次对该对象加锁,将可以加锁成功。而且可以不断的加锁成功多次。但需要注意的是,每次加锁操作必须对应着一次释放锁的操作。 如以下示例是可以运行的(但完全没这么写的必要):
为什么需要可重入锁?先看以下示例(使用内置锁):
以上示例中,a方法调用b方法,两个方法都被内置锁锁定,如果不可重入,那么在调用b的时候当前线程就会等待锁的释放-而实际锁又被自己占用,因此死锁就出现了。而可重入锁就是为了解决这个问题而出现的。
那为什么a方法和b方法可能会需要同时加锁呢?这是因为外部对象可能会单独调用b方法而不去调用a方法!如果b没有进行加锁处理那么可能会导致并发问题。
注意:实际上可重入锁如ReentrantLock在其内部有一个计数器用于保存当前线程对该锁的加锁次数;如果为0是表示当前线程没有获取到该锁。
1.2 读写锁
读写锁内部实际上包含有两个锁对象:一个负责对读操作加锁,一个负责对写操作加锁。读操作不是排他的,也就是说同一时刻可以有多个线程同时占用读锁;而写操作必须是排它的,如果写锁被某个线程占有,那么任何的线程不但获取不到写锁,也获取不到读锁。 使用读写锁能够有效的提高并发;就是因为排它锁不允许同时读,而读写锁允许。
2 内置锁synchronized
对象的内置锁,它有以下特性:
可以使用在方法或者代码段上; 但不可以使用在构造方法上。
它是可重入的;
当synchronized用于同一个对象时,同一时刻只有一个线程能够进入它被synchronized包围的区域。
举个例子,如果某类的A方法和B方法都使用了synchronized关键字,当线程1在调用A方法时,无论线程2想要调用A或者B方法,它都只能等待A的调用完成。这是因为进入synchronized包围区域后,表示的是这个对象的内置锁已经被这个线程获取到,其它要进入synchronized区域的线程都只能等待。
每一个对象都有一个内置锁,对象可以是类实例化后的对象,也可以是类本身。
当内置锁使用在静态方法上时,表示的是对获取的类本身的内置锁,而不是实例化后的内置锁。
使用示例:
内置锁可以简化加锁操作,也能够避免在使用Lock的时候出现一些很常见的问题如死锁等。因此synchronized能够满足需求时可以考虑优先使用内置锁。 但某些复杂场景下可能内置锁无法满足需求,如处理流程是下面这样的:
获取A锁后再获取B锁,然后先释放A锁。这种场景使用内置锁就无法满足。必须使用显示锁(Lock)
3 显式锁Lock
显式锁可以提供比synchronized更加灵活的加锁功能。synchronized的所有使用场景显示锁都能够满足,而且还可以支持更多复杂的操作场景。
Java中的显式锁UML图如下所示:
它主要包含了两个接口和两个实现:
Lock: 最顶层的锁接口,提供加锁与释放锁的接口方法;
ReentrantLock:Lock的一个可重入锁的实现类;
ReadWriteLock: 读写锁接口,提供获取读锁对象与获取写锁对象两个接口方法;
ReentrantReadWriteLock:可重入读写锁的实现类;
3.1 简单示例
显式锁主要的方法就是lock与unlock,先通过一个简单示例来演示锁的使用。
注意此处如果不加锁,在多线程环境下是有导致出问题的。多个线程同时向map中put同一个Key对应的值,最终存储的值将可能是某个线程put进去的,也可能都不是。
注意:加锁后到释放锁前的所有操作都必须被try{}包围起来,并且必须在finally中释放锁。否则如果在释放锁前某个处理抛出异常,将不会进行锁的释放操作,这样的话其它线程就永远获取不到锁了
3.2 锁常用操作
a. Lock接口
?
b. ReadWriteLock接口
方法 | 说明 |
---|---|
readLock | 返回一个读锁对象 |
writeLock | 返回一个写锁对象 |
3.3 读写锁使用示例
如下例,假设有Cache对象对数据按Key、Value进行缓存,写时互斥而读时可同时读,实现如下:
4 信号量Semaphore
信号量是一种轻量锁,它主要用于控制某些有限资源在多线程之间的分配使用。假设最多只允许向数据库建立5个连接,那么同时有5个线程可以使用连接,如果同时请求的线程数超过5个,那么其它未获取许可的线程就只能等待正在运行中的线程释放连接。 Semaphore提供acquire()方法来获取许可,使用release方法来释放许可,初始化的时候可以设置信号量的个数,也可以设置该信号量的公平性参数。
说明
公平性指的是对信号量的请求是否是FIFO(先入先出)的;如果设置成True,那么先调用acquire方法的线程将优先获取到信号量; 如果设置成False,那么将不会保证这个顺序,后提交的可能比在等待中的更加早的获取到信号量。默认情况下是设置成False的。
acquire方法也可以一次性获取多个信号量;当信号量数不够时,将会阻塞直到有信号量被其它线程释放并且数目足够。注意以下场景:如果设置成公平的,当前可使用的信号量为2个,A线程先申请的信号量为3个,B线程后申请两个,那么A与B都将会等待,而不会因为能够满足B线程的需求而优先让B线程获取到足够的信号量。
4.1 信号量使用示例
下例模拟实现连接池。
4.2 方法清单
信号量的使用方法可以分成两类,一类是acquire,一类是release,清单如下:
其中,每个acquire与release方法还有一个带int参数的变种,表示的是获取或者释放指定个数的信号量。
本文为Java并发方面的系列文章之一,感兴趣的可以移步本人头条空间查看Java并发其它文章,后续新的文章也将会陆续发布,欢迎关注及拍砖。 往期:
《Java并发基础-线程状态及转换》
《Java并发基础-Condition使用必知必会》
《Java并发基础-你不一定知道的线程(Thread)特性》
猜你喜欢
- 2024-11-06 信号量限流,高并发场景不得不说的秘密
- 2024-11-06 面试卡在多线程?那就分享几道Java多线程高频面试题,面试不用愁
- 2024-11-06 Java并发系列之Semaphore源码分析
- 2024-11-06 Java多线程与并发 java的并发,多线程,线程模型
- 2024-11-06 66.java并发编程之Semaphore和CountDownLatch使用
- 2024-11-06 Java基础笔试练习(十五) java基础知识试题
- 2024-11-06 72道Java线程面试题,这些面试官必问
- 2024-11-06 Java并发工具:CountDownLatch CyclicBarrier Semaphore快速掌握
- 2024-11-06 死磕 java同步系列之Semaphore源码解析
- 2024-11-06 java中锁扩展Semaphore java中的锁怎么实现
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)