java并发:线程同步机制之Lock
一、初识Lock
Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的,其包路径是:java.util.concurrent.locks.Lock,其核心方法是lock()、unlock()、tryLock(),实现类有ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock,下图展示了Lock接口中定义的方法:

二、ReentrantLock
(1)初识ReentrantLock
Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点,比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java5通过Lock接口提供了更复杂的控制来解决这些问题,《Java并发编程实战》一书有如下描述:


(2)示例
此处我们看下面这两个例子,请注意其中ReentrantLock使用方式的区别:
(1)此处两个方法之间的锁是独立的
package com.test;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
public static void main(String[] args) {
final Countx ct = new Countx();
for (int i = 0; i < 2; i++) {
new Thread() {
@Override
public void run() {
ct.get();
}
}.start();
}
for (int i = 0; i < 2; i++) {
new Thread() {
@Override
public void run() {
ct.put();
}
}.start();
}
}
}
class Countx {
public void get() {
final ReentrantLock lock = new ReentrantLock();
try {
lock.lock();// 加锁
System.out.println(Thread.currentThread().getName() + "get begin");
Thread.sleep(1000L);// 模仿干活
System.out.println(Thread.currentThread().getName() + "get end");
lock.unlock(); // 解锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void put() {
final ReentrantLock lock = new ReentrantLock();
try {
lock.lock();// 加锁
System.out.println(Thread.currentThread().getName() + "put begin");
Thread.sleep(1000L);// 模仿干活
System.out.println(Thread.currentThread().getName() + "put end");
lock.unlock(); // 解锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下(每次运行结果都是不一样的,仔细体会一下):
Thread-1get begin
Thread-0get begin
Thread-2put begin
Thread-3put begin
Thread-0get end
Thread-3put end
Thread-1get end
Thread-2put end
(2)此处两个方法之间使用相同的锁
package com.test;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
public static void main(String[] args) {
final Countx ct = new Countx();
for (int i = 0; i < 2; i++) {
new Thread() {
@Override
public void run() {
ct.get();
}
}.start();
}
for (int i = 0; i < 2; i++) {
new Thread() {
@Override
public void run() {
ct.put();
}
}.start();
}
}
}
class Countx {
final ReentrantLock lock = new ReentrantLock();
public void get() {
// final ReentrantLock lock = new ReentrantLock();
try {
lock.lock();// 加锁
System.out.println(Thread.currentThread().getName() + "get begin");
Thread.sleep(1000L);// 模仿干活
System.out.println(Thread.currentThread().getName() + "get end");
lock.unlock(); // 解锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void put() {
// final ReentrantLock lock = new ReentrantLock();
try {
lock.lock();// 加锁
System.out.println(Thread.currentThread().getName() + "put begin");
Thread.sleep(1000L);// 模仿干活
System.out.println(Thread.currentThread().getName() + "put end");
lock.unlock(); // 解锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下(每次运行结果都是一样的):
Thread-0get begin
Thread-0get end
Thread-1get begin
Thread-1get end
Thread-2put begin
Thread-2put end
Thread-3put begin
Thread-3put end
三、ReadWriteLock
(1)初识ReadWriteLock
Java中的ReadWriteLock是什么?一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果,Java中的ReadWriteLock是Java5中新增的一个接口,提供了readLock和writeLock两种锁机制。一个ReadWriteLock维护一对关联的锁,一个用于只读操作,一个用于写,在没有写线程的情况下,一个读锁可能会同时被多个读线程持有,写锁是独占的。
我们来看一下ReadWriteLock的源码:
public interface ReadWriteLock{
Lock readLock();
Lock writeLock();
}
从源码上面我们可以看出来ReadWriteLock并不是Lock的子接口,只不过ReadWriteLock借助Lock来实现读写两个锁并存、互斥的操作机制。在ReadWriteLock中每次读取共享数据就需要读取锁,当需要修改共享数据时就需要写入锁,看起来好像是两个锁,但是并非如此。
ReentrantReadWriteLock是ReadWriteLock在java.util里面唯一的实现类,主要使用场景是当有很多线程都从某个数据结构中读取数据,而很少有线程对其进行修改。在这种情况下,允许读取器线程共享访问时合适的,写入器线程必须是互斥访问的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则。
ReentrantReadWriteLock的实现里面有以下几个特性:
(1)公平性
(2)重入性
(3)锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就可以从写入锁变成了读取锁,从而实现锁降级的特性。
(4)锁升级
(5)锁获取中断
(6)条件变量
(7)重入数:读取锁和写入锁的数量最大分别是65535。它最多支持65535个写锁和65535个读锁。
概括起来其实就是读写锁的机制:
A、读-读不互斥
B、读-写互斥
C、写-写互斥
(2)示例
示例一:ReadLock和WriteLock单独使用的情况
package demo.thread; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockDemo {
public static void main(String[] args) {
final Count ct = new Count();
for (int i = 0; i < 2; i++) {
new Thread() {
@Override
public void run() {
ct.get();
}
}.start();
}
for (int i = 0; i < 2; i++) {
new Thread() {
@Override
public void run() {
ct.put();
}
}.start();
}
}
} class Count {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public void get() {
rwl.readLock().lock();// 上读锁,其他线程只能读不能写,具有高并发性
try {
System.out.println(Thread.currentThread().getName() + " read start.");
Thread.sleep(1000L);// 模拟干活
System.out.println(Thread.currentThread().getName() + "read end.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock(); // 释放写锁,最好放在finnaly里面
}
} public void put() {
rwl.writeLock().lock();// 上写锁,具有阻塞性
try {
System.out.println(Thread.currentThread().getName() + " write start.");
Thread.sleep(1000L);// 模拟干活
System.out.println(Thread.currentThread().getName() + "write end.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.writeLock().unlock(); // 释放写锁,最好放在finnaly里面
}
} }
运行结果如下:
Thread-1 read start.
Thread-0 read start.
Thread-1read end.
Thread-0read end.
Thread-3 write start.
Thread-3write end.
Thread-2 write start.
Thread-2write end.
从结果上面可以看的出来,读的时候是并发的,写的时候是有顺序的带阻塞机制的
实例二:ReadLock和WriteLock的复杂使用情况,模拟一个有读写数据的场景
private final Map<String, Object> map = new HashMap<String, Object>();// 假设这里面存了数据缓存
private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); public Object readWrite(String id) {
Object value = null;
rwlock.readLock().lock();// 首先开启读锁,从缓存中去取
try {
value = map.get(id);
if (value == null) { // 如果缓存中没有数据,释放读锁,上写锁
rwlock.readLock().unlock();
rwlock.writeLock().lock();
try {
if (value == null) {
value = "aaa"; // 此时可以去数据库中查找,这里简单的模拟一下
}
} finally {
rwlock.writeLock().unlock(); // 释放写锁
}
rwlock.readLock().lock(); // 然后再上读锁
}
} finally {
rwlock.readLock().unlock(); // 最后释放读锁
}
return value;
}
请一定要注意读写锁的获取与释放顺序。
(3)比较分析
ReentrantReadWriteLock与ReentrantLock的比较:
(1)相同点:都是一种显式锁,手动加锁和解锁,都很适合高并发场景
(2)不同点:ReentrantReadWriteLock是对ReentrantLock的复杂扩展,能适合更复杂的业务场景,ReentrantReadWriteLock可以实现一个方法中读写分离的锁机制。而ReentrantLock加锁解锁只有一种机制
四、StampedLock
Java8引入了一个新的读写锁:StampedLock,这个锁更快,而且它提供强大的乐观锁API,这意味着你能以一个较低的代价获得一个读锁, 在这段时间希望没有写操作发生,当这段时间完成后,你可以查询一下锁,看在刚才这段时间是否有写操作发生,然后你可以决定是否需要再试一次或升级锁或放弃。
通常我们的同步锁代码如下:
synchronized(this){
// do operation
}
Java6提供的ReentrantReadWriteLock使用方式如下:
rwlock.writeLock().lock();
try {
// do operation
} finally {
rwlock.writeLock().unlock();
}
ReentrantReadWriteLock、ReentrantLock和synchronized锁都有相同的内存语义,不管怎么说synchronized代码要更容易书写,而ReentrantLock的代码必须严格按照一定的方式来写,否则就会造成严重的问题。StampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小,StampedLock控制锁有三种模式(写,读,乐观读)
参考资料:
Java 8新特性StampedLock
(1)http://www.importnew.com/14941.html
(2)http://www.jdon.com/idea/java/java-8-stampedlock.html
===============深入学习===AbstractQueuedSynchronizer=================
此处贴出一些资源,后续会研究这部分内容
(1)http://ifeve.com/jdk1-8-abstractqueuedsynchronizer/
(2)http://ifeve.com/introduce-abstractqueuedsynchronizer/
java并发:线程同步机制之Lock的更多相关文章
- Java 并发 线程同步
Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...
- java synchronized 线程同步机制详解
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...
- Java并发——线程同步Volatile与Synchronized详解
0. 前言 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52370068 面试时很可能遇到这样一个问题:使用volatile修饰in ...
- Java多线程编程(4)--线程同步机制
一.锁 1.锁的概念 线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...
- Java多线程 | 02 | 线程同步机制
同步机制简介 线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.Java平台提供的线程同步机制包括: 锁,volatile关键字,final关键字,static关键字,以 ...
- 【总结】Java线程同步机制深刻阐述
原文:http://hxraid.iteye.com/blog/667437 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread ...
- Java分享笔记:创建多线程 & 线程同步机制
[1] 创建多线程的两种方式 1.1 通过继承Thread类创建多线程 1.定义Thread类的子类,重写run()方法,在run()方法体中编写子线程要执行的功能. 2.创建子线程的实例对象,相当于 ...
- Java多线程的同步机制(synchronized)
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...
- java两种同步机制的实现 synchronized和reentrantlock
java两种同步机制的实现 synchronized和reentrantlock 双11加保障过去一周,趁现在有空,写一点硬货,因为在进入阿里之后工作域的原因之前很多java知识点很少用,所以记录一下 ...
随机推荐
- 移动语义 && 函数调用过程中的 lvalue
当以一个函数内的临时变量对象作为另一个函数的形参的时候,原函数内的临时对象即 rvalue,就会成为此函数内的 lvalue. 这样会重新导致效率低下,因为造成了大量复制操作. <utility ...
- nyoj 170 网络的可靠性
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=170 思路:统计每个节点的度,将度为1的节点消去所需要的最少的边即为答案. 代码: #in ...
- 前端mock数据之mockjax和mockjson
用处 在前后台共同进行一个项目的时候常会遇到一种情景, 后台定义好接口,前端按照接口进行开发, 当前端开发完成后台接口却还没有开发完成, 这个时候要进行接口测试, 只能等后台开发完成才能测试, 在这中 ...
- 【MVC 4】5.SportsSore —— 一个真实的应用程序
作者:[美]Adam Freeman 来源:<精通ASP.NET MVC 4> 前面建立的都是简单的MVC程序,现在到了吧所有事情综合在一起,以建立一个简单但真实的电子商务应用 ...
- vim支持lua
1. ncurses 安装 官网下载:http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz CSDN 下载:http://download.csd ...
- hdu-5895 Mathematician QSC(数学)
题目链接: Mathematician QSC Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Jav ...
- leetcode-Single NumberII
https://leetcode.com/problems/single-number-ii/ 很无耻的又一次使用了黑暗料理... class Solution: # @param {integer[ ...
- Mango DS Traning #49 ---线段树3 解题手记
Training address: http://acm.hust.edu.cn/vjudge/contest/view.action?cid=38994#overview B.Xenia and B ...
- JavaScript 中的原型声明和用法总结
下面是自己写的一个关于js的拖拽的原型声明:代码如下 需要注意的问题包括: 1.this的指向到底是指向谁--弄清楚所指的对象 2.call()方法的使用 3.直接将父级原型赋给子级与使用for将其赋 ...
- HTML5之应用缓存---manifest---缓存使用----HTML5的manifest缓存
相信来查这一类问题的都是遇到问题或者是初学者吧! 没关系相信你认真看过之后就会知道明白的 这是HTML5新加的特性 HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连 ...