热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java多线程总结(8)concurrent.locks包下的锁机制的使用

1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl

1 Lock与ReadWriteLock

1.1 Lock

public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}

  在Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?
  lock()
  首先lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
  由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的形式:

Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){

}finally{
lock.unlock(); //释放锁
}

  tryLock()和tryLock(long time, TimeUnit unit)
  tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
  tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
  通过tryLock来获取锁的基本形式:

Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
} catch(Exception ex){

} finally {
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}

  lockInterruptibly()
  lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
  由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。

  lockInterruptibly()一般的使用形式:

public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}

finally {
lock.unlock();
}

}

  注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。
  因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,在进行等待的情况下,是可以响应中断的。
  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

1.2 Lock实现类ReentrantLock

  ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。下面通过一些实例具体看一下如何使用ReentrantLock。
  tryLock()

public class TrylockMethodTest {

public static void main(String[] args) {
SharedDataService service = new SharedDataService();

new Thread(new Runnable() {

@Override
public void run() {
service.put("msg");
}
}).start();

new Thread(new Runnable() {

@Override
public void run() {
service.put("Message");
}
}).start();
}

static class SharedDataService {
private List data = new ArrayList();
// 创建一个非公平的可重入锁
private Lock lock = new ReentrantLock();

public void put(String msg) {
/*
* Acquires the lock only if it is free at the time of invocation.
* 返回true说明获取锁成功
*/

if(lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName()+"得到了锁");
data.add(msg);
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"存入数据"+msg);
} catch (Exception e) {
// TODO: handle exception
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放了锁");
}
} else {
System.out.println(Thread.currentThread().getName()+"获取锁失败!");
}
}
}
}

  运行结果:

Thread-0得到了锁
Thread-1获取锁失败!
Thread-0存入数据msg
Thread-0释放了锁

  lockInterruptibly()

public class LockInterruptiblyMethodTest {

public static void main(String[] args) throws InterruptedException {
SharedDataService service = new SharedDataService();

Thread thread0 = new Thread(new Runnable() {

@Override
public void run() {
try {
service.put("msg");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"在等待获取锁时被中断!");
}
}
});
thread0.start();

Thread thread1 = new Thread(new Runnable() {

@Override
public void run() {
try {
service.put("Message");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"在等待获取锁时被中断!");
}
}
});
thread1.start();

// 主线程中中断thread1等待获取锁的阻塞状态
Thread.sleep(1000);
thread1.interrupt();
}

static class SharedDataService {
private List data = new ArrayList();
// 创建一个非公平的可重入锁
private Lock lock = new ReentrantLock();

public void put(String msg) throws InterruptedException {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName()+"得到了锁");
data.add(msg);
Thread.sleep(4000);
System.out.println(Thread.currentThread().getName()+"存入数据"+msg);
} catch (Exception e) {
// TODO: handle exception
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放了锁");
}
}
}
}

  运行结果:
  这里写图片描述

1.3 ReadWriteLock

public interface ReadWriteLock {
/**
* 返回用于读操作的锁
*/

Lock readLock();

/**
* 返回用于写操作的锁
*/

Lock writeLock();
}

  一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。

1.4 ReadWriteLock的实现类ReentrantReadWriteLock

  下面通过几个例子来看一下ReentrantReadWriteLock具体用法:(主要看readLock和writeLock)
  假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:(代码比较简单,省略了)
        这里写图片描述
  而采用ReadWriteLock的读操作锁如下:

public class ReadLockTest {

public static void main(String[] args) {
SharedDataService service = new SharedDataService();

new Thread(new Runnable() {

@Override
public void run() {
service.get();
}
}).start();

new Thread(new Runnable() {

@Override
public void run() {
service.get();
}
}).start();
}

static class SharedDataService {
// 创建一个读写锁
private ReadWriteLock rwl = new ReentrantReadWriteLock();

public void get() {
// 获取读操作的锁ReadLock
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"得到了锁");
for (int i = 0; i <6; i++) {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName()+"读取数据");
}
} catch (Exception e) {
// TODO: handle exception
} finally {
rwl.readLock().unlock();// 释放ReadLock
System.out.println(Thread.currentThread().getName()+"读操作完毕释放了锁");
}
}
}
}

        这里写图片描述
  说明采用读写锁,thread0和thread1在同时进行读操作。这样就大大提升了读操作的效率。
  不过要注意的是:

  1. 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
  2. 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
  在性能上来说,如果竞争资源不激烈,Lock和synchronized的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

1.5 利用读写锁实现简单的缓存系统

关于缓存系统:
  缓存系统一般位于用户和数据库中间的一个环节,用户直接访问数据库的时间是远大于直接访问内存,所以有了缓存区后用户访问数据时,先访问缓存区,当缓存区有用户需要的数据时直接拿走,当缓存区没有这样的数据,访问数据库并把访问所得的数据放在缓存区,这样当下一个需要这个数据的用户就直接访问内存即可得到。
  JDK文档中给出的Demo:

public class CashedDataDemo {

public static void main(String[] args) {

CachedDataService cacheService = new CachedDataService();
for (int i = 0; i <10; i++) {
new Thread(new Runnable() {
public void run() {
cacheService.processCachedData();
}
}).start();
}

}

static class CachedDataService {
String data;
volatile boolean cacheValid = false; // 缓存是否有效,即是否有数据
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

void processCachedData() {
rwl.readLock().lock(); // 上读锁
if (!cacheValid) {
/* 在获取写锁之前一定要先释放读锁 */
rwl.readLock().unlock();
/* 其中一个线程获得写锁 */
rwl.writeLock().lock();
try {
/*
* 注意此处需要再次检查缓存的状态,因为其他的线程可能阻塞在获取写锁的地方,
* 当实际写缓存的线程写完数据释放写锁时,其他的线程仍然获取到写锁,再此写数据了。
*/

if (!cacheValid) {
data = "get new data!";
System.out.println(Thread.currentThread().getName() + "写数据到缓存:" + data);
Thread.sleep(2000);
cacheValid = true; // 设置缓存状态
}
} catch(InterruptedException e) {

}finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}

try {
System.out.println(Thread.currentThread().getName() + "从缓存中获取到数据:" + data);
} finally {
rwl.readLock().unlock();
}
}
}
}

  输出:
      这里写图片描述

ReentrantReadWriteLocks can be used to improve concurrency in some uses of some kinds of Collections. This is typically worthwhile only when the collections are expected to be large, accessed by more reader threads than writer threads, and entail operations with overhead that outweighs synchronization overhead. For example, here is a class using a TreeMap that is expected to be large and concurrently accessed.

 class RWDictionary {
private final Map m = new TreeMap();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();

public Data get(String key) {
r.lock();
try { return m.get(key); }
finally { r.unlock(); }
}
public String[] allKeys() {
r.lock();
try { return m.keySet().toArray(); }
finally { r.unlock(); }
}
public Data put(String key, Data value) {
w.lock();
try { return m.put(key, value); }
finally { w.unlock(); }
}
public void clear() {
w.lock();
try { m.clear(); }
finally { w.unlock(); }
}
}

2 Condition

  Condition可以替代传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。传统线程的通信方式,Condition都可以实现。

  注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。Condition的强大之处在于它可以为多个线程间建立不同的Condition。

  看JDK文档中的一个例子:假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition 实例来做到这一点。

——其实就是java.util.concurrent.ArrayBlockingQueue的功能

class BoundedBuffer {
final Lock lock = new ReentrantLock(); //锁对象
final Condition notFull = lock.newCondition(); //缓冲区未满,写数据
final Condition notEmpty = lock.newCondition(); //缓冲区非空,读数据

final Object[] items = new Object[100];//缓存队列
int putptr; //写索引
int takeptr; //读索引
int count; //队列中数据数目

//写
public void put(Object x) throws InterruptedException {
lock.lock(); //锁定
try {
// 如果队列满,则阻塞<写线程>
while (count == items.length) {
notFull.await();
}
// 写入队列,并更新写索引
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;

// 唤醒<读线程>
notEmpty.signal();
} finally {
lock.unlock();//解除锁定
}
}

//读
public Object take() throws InterruptedException {
lock.lock(); //锁定
try {
// 如果队列空,则阻塞<读线程>
while (count == 0) {
notEmpty.await();
}

//读取队列,并更新读索引
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;

// 唤醒<写线程>
notFull.signal();
return x;
} finally {
lock.unlock();//解除锁定
}
}
}

  参考:
http://www.cnblogs.com/dolphin0520/p/3923167.html
http://my.oschina.net/91jason/blog/385500?fromerr=JhsMinq6
http://blog.csdn.net/it_man/article/details/8972001


推荐阅读
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
author-avatar
x47608476
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有