java 读写锁 java多线程读写锁被阻塞后,一旦解锁,会自动运行么

博客访问: 67745
博文数量: 138
博客积分: 0
博客等级: 民兵
技术积分: 869
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
原文地址: 作者:
读写锁(read-write lock)
&&& 在一些程序中存在读者写者问题,也就是说,对某些资源的访问会& 存在两种可能的情况,一种是访问必须是排它行的,就是独占的意思,这称作写操作;另一种情况就是访问方式可以是共享的,就是说可以有多个线程同时去访问某个资源,这种就称作读操作。这个问题模型是从对文件的读写操作中引申出来的。
&&& 读写锁比起mutex具有更高的适用性,具有更高的并行性,可以有多个线程同时占用读模式的读写锁,但是只能有一个线程占用写模式的读写锁,读写锁的三种状态:
1.当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞
2.当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行加锁的线程将会被阻塞
3.当读写锁在读模式的锁状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁的请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求则长期阻塞。
&&& 读写锁最适用于对数据结构的读操作次数多于写操作的场合,因为,读模式锁定时可以共享,而写模式锁定时只能某个线程独占资源,因而,读写锁也可以叫做个共享-独占锁。
&&& 处理读者-写者问题的两种常见策略是强读者同步(strong reader synchronization)和强写者同步(strong writer synchronization).&& &在强读者同步中,总是给读者更高的优先权,只要写者当前没有进行写操作,读者就可以获得访问权限;而在强写者同步中,则往往将优先权交付给写者,而读者只能等到所有正在等待的或者是正在执行的写者结束以后才能执行。关于读者-写者模型中,由于读者往往会要求查看最新的信息记录,所以航班订票系统往往会使用强写者同步策略,而图书馆查阅系统则采用强读者同步策略。
&&& 读写锁机制是由posix提供的,如果写者没有持有读写锁,那么所有的读者多可以持有这把锁,而一旦有某个写者阻塞在上锁的时候,那么就由posix系统来决定是否允许读者获取该锁。
二 读写锁相关的API
1.初始化和销毁读写锁
&&& 对于读写锁变量的初始化可以有两种方式,一种是通过给一个静态分配的读写锁赋予常值PTHREAD_RWLOCK_INITIALIZER来初始化它,另一种方法就是通过调用pthread_rwlock_init()来动态的初始化。而当某个线程不再需要读写锁的时候,可以通过调用pthread_rwlock_destroy来销毁该锁。函数原型如下:
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
这两个函数如果执行成功均返回0,如果出错则返回错误码。
在释放某个读写锁占用的内存之前,要先通过pthread_rwlock_destroy对读写锁进行清理,释放由pthread_rwlock_init所分配的资源。
在初始化某个读写锁的时候,如果属性指针attr是个空指针的话,表示默认的属性;如果想要使用非默认属性,则要使用到下面的两个函数:
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockatttr_t *attr);
这两个函数同样的,如果执行成功返回0,失败返回错误码。
这里还需要说明的是,当初始化读写锁完毕以后呢,该锁就处于一个非锁定状态。
数据类型为pthread_rwlockattr_t的某个属性对象一旦初始化了,就可以通过不同的函数调用来启用或者是禁用某个特定的属性。
2.获取和释放读写锁
读写锁的数据类型是pthread_rwlock_t,如果这个数据类型中的某个变量是静态分配的,那么可以通过给它赋予常值PTHREAD_RWLOCK_INITIALIZAR来初始化它。pthread_rwlock_rdlock()用来获取读出锁,如果相应的读出锁已经被某个写入者占有,那么就阻塞调用线程。pthread_rwlock_wrlock()用来获取一个写入锁,如果相应的写入锁已经被其它写入者或者一个或多个读出者占有,那么就阻塞该调用线程;pthread_rwlock_unlock()用来释放一个读出或者写入锁。函数原型如下:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
这三个函数若调用成功则返回0,失败就返回错误码。要注意的是其中获取锁的两个函数的操作都是阻塞操作,也就是说获取不到锁的话,那么调用线程不是立即返回,而是阻塞执行。有写情况下,这种阻塞式的获取所得方式可能不是很适用,所以,接下来引入两个采用非阻塞方式获取读写锁的函数pthread_rwlock_tryrdlock()和pthread_rwlock_trywrlock(),非阻塞方式下获取锁的时候,如果不能马上获取到,就会立即返回一个EBUSY错误,而不是把调用线程投入到睡眠等待。函数原型如下:
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);
同样地,这两个函数调用成功返回0,失败返回错误码。
读者-写者模型来实现多线程同步问题(原文)
阅读(83) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。当前位置: >>
Java中的多线程你只要看这一篇就够了
发布于:09月01日 &
来自:简书 作者:知米丶无忌 链接:/p/40d4c7aebd66 (点击尾部阅读原文前往) 引 如果对什么是线程、、什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。 说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型” 很多人都对其中的一些概念不够明确,如同步、、并发、,让我们先建立一个数据字典,以免产生误会 多线程: 指的是这个程序(一个进程)运行时产生了不止一个线程 并行与并发: 并行: 多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时 并发: 通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。 并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力 并发与并行 线程安全: 经常用来描绘一段代码。 指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。 这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。 反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码: oid transferMoney(User from, User to, float amount){
to.setMoney(to.getBalance() + amount);
from.setMoney(from.getBalance() - amount);
} 同步: Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。 如上面的代码简单加入@synchronized关键字。 在保证结果准确的同时,提高性能,才是优秀的程序。 线程安全的优先级高于性能 好了,让我们开始吧。 我准备分成几部分来总结涉及到多线程的内容: 1、、扎好马步: 线程的状态 2、、 每个对象都有的方法(机制) 3、、 太祖长拳: 基本线程类 4、、 九阴真经: 高级多线程控制类 扎好马步:线程的状态 先来两张图: 线程状态 线程状态转换 各种状态一目了然,值得一提的是"blocked"这个状态: 线程在Running的过程中可能会遇到阻塞(Blocked)情况 1、、 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,、、待JVM的调度 2、、 调用wait(),使该线程处于、、待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable) 3、、 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable) 此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。 Thread类中的yield方法可以让一个running状态的线程转入runnable 内功心法:每个对象都有的方法(机制) synchronized, wait, notify 是任何对象都具有的同步工具。 让我们先来了解他们 monitor 他们是应用于同步问题的人工线程调度工具。 讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。 在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用 wait/notify必须存在于synchronized块中。 并且,这三个关键字针对的是同一个监视器(某对象的监视器)。 这意味着wait之后,其他线程可以进入同步块执行 当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。 也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常 再讲用法: synchronized单独使用: 代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容 public
Thread1 implements Runnable {
synchronized(lock){
..do something
} 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。 更进一步,如果修饰的是static方法,则锁定该类所有实例 public
Thread1 implements Runnable {
public synchronized
..do something
} synchronized, wait, notify结合:典型场景生产者消费者问题 /**
* 生产者生产出来的产品交给店员
public synchronized void produce ()
if ( this .product &= MAX_PRODUCT)
System.out.println( "产品已满,请稍候再生产" );
catch (InterruptedException e)
e.printStackTrace();
this .product++;
System.out.println( "生产者生产第"
this .product +
"个产品." );
notifyAll();
//通知、、待区的消费者可以取出产品了
* 消费者从店员取产品
public synchronized void consume ()
if ( this .product &= MIN_PRODUCT)
System.out.println( "缺货,稍候再取" );
(InterruptedException e)
e.printStackTrace();
System.out.println( "消费者取走了第"
this .product +
"个产品." );
this .product--;
notifyAll();
//通知、、待去的生产者可以生产产品了
} volatile 多线程的内存模型: main memory(主存)、、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save) volatile 针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。 其实道理上讲同一实例的同一属性本身只有一个副本。 但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。 在线程安全的情况下加volatile会牺牲性能 太祖长拳:基本线程类 基本线程类指的是Thread类,Runnable接口,Callable接口 Thread 类实现了Runnable接口,启动一个线程的方法: MyThread my =
MyThread();
  my.start(); Thread类相关方法: //当前线程可转让cpu控制权,让别的就绪状态线程运行(切换) public static
Thread.yield()
//暂停一段时间 public static
Thread.sleep() //在一个线程中调用other.join(),将、、待other执行完后才继续本线程 public join () / /后两个函数皆可以被打断 public interrupte () 关于中断 :它并不像stop方法那样会中断一个正在运行的线程。 线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。 终端只会影响到wait状态、、sleep状态和join状态。 被打断的线程会抛出InterruptedException Thread.interrupted()检查当前线程是否发生中断,返回boolean synchronized在获锁的过程中是不能被中断的 中断是一个状态!interrupt()方法只是将这个状态置为true而已。 所以说正常运行的程序不去检测状态,就不会终止,而wait、、阻塞方法会去检查并抛出异常。 如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体 Thread类最佳实践: 写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。 在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的 如何获取线程中的异常 不能用try,catch来获取线程中的异常 Runnable 与Thread类似 Callable future模式: 并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。 其中Future对象用来存放该线程的返回值以及状态 ExecutorService e = Executors.newFixedThreadPool( 3 );
//submit方法有多重参数版本,及支持callable也能够支持runnable接口类型. Future future = e.submit( new
myCallable());
future.isDone()
//return true,false
无阻塞 future.get()
// return 返回值,阻塞直到该线程运行结束 九阴真经:高级多线程控制类 以上都属于内功心法,接下来是实际项目中常用到的工具了,Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序 1、、ThreadLocal类 用处:保存线程的独立变量。 对一个线程类(继承自Thread) 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 常用于用户登录控制,如记录session信息 实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。 功能还是一个map。 )以本身为key,以目标为value 主要方法是get()和set(T a),set之后在map里维护一个threadLocal -& a,get时将a返回。 ThreadLocal是一个特殊的容器 2、原子类(AtomicInteger、AtomicBoolean……) 如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则、、同于synchronized //返回值为boolean pareAndSet( int
expect, int
update) 该方法可用于实现乐观锁,考虑文中最初提到的如下场景:a给b付款10元,a扣了10元,b要加10元。 此时c给b2元,但是b的加十元代码约为: if (pareAndSet(old, value)){
//try again
// if that fails, rollback and log } AtomicReference 对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA 这时候,AtomicStampedReference就派上用场了。 这也是一个很常用的思路,即加上版本号 3、、Lock类 lock: 在java.util.concurrent包内。 共有三个实现: ReentrantLock ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock 主要目的是和synchronized一样, 两者全都是为了解决同步问题,处理资源争端而产生的技术。 功能类似但有一些区别 区别如下 : 1、、lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序) 2、、 提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本 3、、 本质上和监视器锁(即synchronized是一样的) 4、、 能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难 5、、 和Condition类的结合 6、、 性能更高,对比如下图: synchronized和Lock性能对比 ReentrantLock
可重入的意义在于持有锁的线程可以继续持有,并且要释放对、、的次数后才真正释放该锁 使用方法是: 1、、先new一个实例 static
ReentrantLock r= new
ReentrantLock(); 2、、加锁 r.lock()或r.lockInterruptibly(); 此处也是个不同,后者可被打断。 当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。 (如果使用后者,必须throw interruptable exception 或catch) 3、、释放锁 r.unlock() 必须做!何为必须做呢,要放在finally里面。 以防止异常跳出了正常流程,导致灾难。 这里补充一个小知识点,finally是可以信任的:经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证 ReentrantReadWriteLock 可重入读写锁(读写锁的一个实现) ReentrantReadWriteLock lock =
ReentrantReadWriteLock()
  ReadLock r = lock.readLock();
  WriteLock w = lock.writeLock(); 两者都有lock,unlock方法。 写写,写读互斥、、读读不互斥。 可以实现并发读的高效线程安全代码 4、、容器类 这里就讨论比较常用的两个: BlockingQueue ConcurrentHashMap BlockingQueue 阻塞队列。 该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。 类似于一个管  道,特别适用于先进先出策略的一些应用场景。 普通的queue接口主要实现有PriorityQueue(优先队列),有兴趣可以研究 BlockingQueue在队列的基础上添加了多线程协作的功能: BlockingQueue 除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。 put会在队列满的时候阻塞,直到有空间时被唤醒、、take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。 用于生产者-消费者模型尤其好用,堪称神器 常见的阻塞队列有: ArrayListBlockingQueue LinkedListBlockingQueue DelayQueue SynchronousQueue ConcurrentHashMap 高效的线程安全哈希map。 请对比hashTable , concurrentHashMap, HashMap 5、、管理类 管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装 了解到的值得一提的管理类:ThreadPoolExecutor和 JMX框架下的系统级管理类 ThreadMXBean ThreadPoolExecutor 如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便: ExecutorService e = Executors.newCachedThreadPool();
ExecutorService e = Executors.newSingleThreadExecutor();
ExecutorService e = Executors.newFixedThreadPool( 3 );
// 第一种是可变大小线程池,按照任务数来分配线程,
// 第二种是单线程池,相当于FixedThreadPool(1)
// 第三种是固定大小线程池
// 然后运行
e.execute( new
MyRunnableImpl()); 该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们全都是ThreadPoolExecutor类的各种实现版本。 请参见javadoc: ThreadPoolExecutor参数解释 翻译一下: corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程 maximumPoolSize:线程最大值,线程的增长始终不会超过该值 keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。 回收前处于wait状态 unit: 时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS workQueue:待入任务(Runnable)的、、待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving) threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入 ● 本文编号1898,以后想阅读这篇文章直接输入 1898 即可 ●本文分类“ Java ” , 搜索分类名可以获得相关文章 ●输入m可以获取到文章目录 本文内容的相关公众号推荐 Java编程 ↓ ↓ ↓ 安卓开发 ↓ ↓ ↓ 更多推荐请看 《 》 涵盖:程序人生、.NET、大数据技术、算法与数据结构、黑客技术与网络安全、运维、安卓开发、Java、Python、iOS开发、数据库、Linux、Web开发、C/C++、前端开发、。 传播计算机学习经验、、推荐计算机优秀资源:点击前往《 》
#撸片儿#小伙子在商场电梯摸路人老爷们儿的手,各路神佛表现都不同有几个直男大叔气得要动手打人怎么这几个兄弟看起来上勾了#撸片儿#小伙子在商场
对这组偷情CP,愉鸡只想送出一款wuli韬韬表情包↓↓此事一出,可以想象荷兰旅馆从业者会对本行业的前景多么担忧:现在的人啊,好好的酒店放着不
大家一起动动脑,乐一乐!如果每一张图你都能看得出来,那恭喜你离痴呆可能还有很远.......1、、在这树上你能看到10张面孔吗?2、、你能
看完点击"电影达人"看更多短片&#13看完点击"电影达人"看更多短片
看完点击"电影达人"看更多短片&#13看完点击"电影达人"看更多短片
由于男主角一直进不了状态,拍摄先暂停,上原亚衣再次拿起剧本,开始熟悉拍摄当天图中为男女主角,日本AV中在拍摄前及私下,是禁止男女主角接触的,让 线程 阻塞--&执行
怎么再从 执行--&阻塞 !!
[问题点数:50分,结帖人saksak]
让 线程 阻塞--&执行
怎么再从 执行--&阻塞 !!
[问题点数:50分,结帖人saksak]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2007年9月 C/C++大版内专家分月排行榜第三2007年8月 C/C++大版内专家分月排行榜第三2006年11月 C/C++大版内专家分月排行榜第三
2009年4月 Linux/Unix社区大版内专家分月排行榜第三
2009年4月 总版技术专家分月排行榜第一
2009年11月 Linux/Unix社区大版内专家分月排行榜第一2009年6月 Linux/Unix社区大版内专家分月排行榜第一2009年4月 C/C++大版内专家分月排行榜第一2009年3月 C/C++大版内专家分月排行榜第一2009年3月 Linux/Unix社区大版内专家分月排行榜第一2009年2月 Linux/Unix社区大版内专家分月排行榜第一
2009年4月 Linux/Unix社区大版内专家分月排行榜第三
2009年4月 Linux/Unix社区大版内专家分月排行榜第三
2009年4月 Linux/Unix社区大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。java技术(25)
& &&ReentrantLock锁是jdk1.5之后加的轻量级锁,相对以前的重量级锁,它有很多的优势。ReentrantLock只支持独占方式的获取操作,它将同步状态用于保存锁获取操作的次数,并且还维护一个owner变量来保存当前所有的线程标识符,只有当线程获取或者释放锁的时候才会修改这个变量。
& &1. 可重入锁的源码分析:
当我打开的ReentrantLock源码的时候发现它的代码却是非常简单的。总共有三个内部类:第一个抽象内部类Sync (abstract static class Sync extends AbstractQueuedSynchronize)
直接继承AQS 我们可以大概了解一下什么 AQS? &&AQS是Java并发类库的基础,其提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础。使用的方法是继承,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性,因此子类对于状态的把握,需要使用
这个同步器提供的常用方法对状态进行操作;
第二个内部类NonfairSync(非公平锁):非公平锁是直接获取锁,没有维护等待队列.第三个内部类FairSync(公平锁):当遇到阻塞的时候,会把请求锁的进程添加到维护等待队列,下次释放锁的时候会从队列的头节头进行处理。
锁的申请和释放都是成对出现的,我们先来看一下ReentrantLock对常规lock和unlock的处理.对于常规的独占锁,ReentrantLock用0和1
分别表示是否有线程持有锁。0代表没有线程持有锁 ,如果有线程申请锁就会把状态改为1,如果释放锁了,就会把状态改为0;
我们来看一下源码:
&lock方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
acquire(1);
}compareAndSetState比较当前的状态是否是0,如果是0的同时,会把当前状态设置成1。如果两个步骤都完成,证明获取锁成功,同时设置进程状态为当前进程。
unlock方法:
public void unlock() {
sync.release(1);
}简单的把当前状态减1.
可重入方法trylock:
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}再看nonfairTryAcquire方法:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
else if (current == getExclusiveOwnerThread()) {
int nextc = c +
if (nextc & 0) // overflow
throw new Error(&Maximum lock count exceeded&);
setState(nextc);
}如果当前锁状态为0,那么直接获取锁并且返回,如果锁的状态不是0,证明有线程持有锁,再比较当前线程与请求线程是否是同一条线程,则会把累加当前持有的进程数,否则获取锁失败。
目前为此,我们大概已经可以明白可重入锁的实现了,主要是借助AQS 框架来实现,后面会再分析AQS。
& &2. &读写锁的源码分析:
& & & & & &读写锁和可重入锁是都是基于AQS 来实现的,所以读写内部还是会有一个Sync类。除此之外还有两个类:ReadLock和WriteLock类,不过这两个类都是用同一个 Sync的,当初没有源码的时候,我以为会有两个Sync类,所以读写锁是用一个AQS子类 同时管理讯读取加锁和写入加锁。AQS在内部维护一个等待线程队列,其中记录了某个线程是独占访问(相当于写)还是共享访问(相当于读)。当锁可用时,如果位于队列头部的线程是执行写入操作,那么线程会得到这个锁,如果位于队列头部的线程是读取访问,那么队列中在第一个写入线程之前的所有线程都将获得这个锁。这是一种没有读或者写优先等待的策略。
& 下面简单的分析一下(因为和上面太多相同):
& 看一下ReadLock的lock方法:
public void lock() {
sync.acquireShared(1);
&这里申请的是共享锁。
WriteLock的lock方法:
public void lock() {
sync.acquire(1);
这里申请的是独占锁。从这里可以看到AQS的强大,下面我们还是重点看一下AQS。
& 3. AQS分析 &&
& & 前面已经说过了,AQS用一个int来表示的状态,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性,因此AQS提供getState,setState 和compareAndSetState
这三个原子方法.常用的获取锁和释放锁的流程:
获取操作过程如下:
if(尝试获取成功){
加入等待队列;park自己
释放操作:
if(尝试释放成功){
unpark等待队列中第一个节点
return false
& &在多线程中也必须保证等待队列是线程安全,而且是非阻塞式的。我们来看一下队列的实现:
& & & &&Node类里面有分别pre指向上一个节点,next指向下一个节点。同时有SHARED共享和EXCLUSIVE独占两种模式.我们先接着从前面的ReentrantLock的lock 方法分 & &
析,如果没有获取锁就会调用&acquire(1) 方法。我们接着AQS里面的方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}& & &&tryAcquire再次尝试获取锁,如果还是失败,就会添加到队列:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
backup to full enq on failure
Node pred =
if (pred != null) {
node.prev =
if (compareAndSetTail(pred, node)) {
pred.next =
enq(node);
}首先:判断尾节点是否为空,如果不为空,直接插入到尾部,如果为空则做特殊处理
我们再来看一下: final boolean acquireQueued(final Node node, int arg) {
boolean failed =
boolean interrupted =
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = // help GC
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted =
} finally {
if (failed)
cancelAcquire(node);
1. 获取当前节点的前驱节点;
需要获取当前节点的前驱节点,而头结点所对应的含义是当前站有锁且正在运行。
2. 当前驱节点是头结点并且能够获取状态,代表该当前节点占有锁;
如果满足上述条件,那么代表能够占有锁,根据节点对锁占有的含义,设置头结点为当前节点。
3. 否则进入等待状态。
如果没有轮到当前节点运行,那么将当前线程从线程调度器上摘下,也就是进入等待状态。
我们再来看一下AQS的release方法:
public final boolean release(int arg) {
if (tryRelease(arg)) {
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
}1. 尝试释放状态;
tryRelease能够保证原子化的将状态设置回去,当然需要使用compareAndSet来保证。如果释放状态成功过之后,将会进入后继节点的唤醒过程。一般由子类实现。
2. 唤醒当前节点的后继节点所包含的线程。
通过LockSupport的unpark方法将休眠中的线程唤醒,让其继续acquire状态。
我们再来看一下unparkSuccessor方法:
private void unparkSuccessor(Node node) {
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling.
It is OK if this
* fails or if status is changed by waiting thread.
int ws = node.waitS
if (ws & 0)
compareAndSetWaitStatus(node, ws, 0);
* Thread to unpark is held in successor, which is normally
* just the next node.
But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
Node s = node.
if (s == null || s.waitStatus & 0) {
for (Node t = t != null && t != t = t.prev)
if (t.waitStatus &= 0)
if (s != null)
LockSupport.unpark(s.thread);
}先取出当前节点的那个一个节点,如果为空,则从最后一个节点一直遍历到第一个节点,直到找到阻塞的节点,同时会唤醒该节点.
下面我们再看一下利用AQS自定义的锁:
package javaT
import java.util.concurrent.locks.AbstractQueuedLongS
public class MyAQSLock {
final Sync sync = new Sync();
public void signal() {
sync.releaseShared(0);
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(0);
private class Sync extends AbstractQueuedLongSynchronizer {
protected long tryAcquireShared(long arg) {
return (getState() ==
1) ? 1 : -1;
protected boolean tryReleaseShared(long arg) {
setState(1);
这里只是一个简单的锁,用0表示关闭,1表示打开. 当调用 await方法的时候,然后会调用&tryAcquireShared方法,如果已经打开了闭锁,那么就允许通过。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:38143次
排名:千里之外
原创:43篇
转载:10篇
(1)(1)(1)(1)(11)(21)(1)(1)(1)(2)(4)(4)(4)

我要回帖

更多关于 java多线程读写锁 的文章

 

随机推荐