synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候
下面细细道来……

先说第一种情况,ReentrantLock的lock机制有2种,忽略中断锁和响应中断锁,这给我们带来了很大的灵活性。比如:如果A、B2个线程去竞争锁,A线程得到了锁,B线程等待,但是A线程这个时候实在有太多事情要处理,就是一直不返回,B线程可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。这个时候ReentrantLock就提供了2种机制,第一,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);第二,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个锁的到来,完全放弃。(如果你没有了解java的中断机制,请参考下相关资料,再回头看这篇文章,80%的人根本没有真正理解什么是java的中断,呵呵)

这里来做个试验,首先搞一个Buffer类,它有读操作和写操作,为了不读到脏数据,写和读都需要加锁,我们先用synchronized原语来加锁,如下:

package cn.vicky.chapt10;

/**
*
* @author Vicky.H
*/
public class Buffer { private Object lock; public Buffer() {
lock = this;
} public void write() {
synchronized (lock) {
long startTime = System.currentTimeMillis();
System.out.println("开始往这个buff写入数据…");
for (;;)// 模拟要处理很长时间
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE) {
break;
}
}
System.out.println("终于写完了");
}
} public void read() {
synchronized (lock) {
System.out.println("从这个buff读数据");
}
} public static void main(String[] args) {
Buffer buff = new Buffer(); final Writer writer = new Writer(buff);
final Reader reader = new Reader(buff); writer.start();
reader.start(); new Thread(new Runnable() { @Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
//等5秒钟去中断读
if (System.currentTimeMillis()
- start > 5000) {
System.out.println("不等了,尝试中断");
reader.interrupt();
break;
} } }
}).start();
// 我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,
// 就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,
// 它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,
// 让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。
}
} class Writer extends Thread { private Buffer buff; public Writer(Buffer buff) {
this.buff = buff;
} @Override
public void run() {
buff.write();
}
} class Reader extends Thread { private Buffer buff; public Reader(Buffer buff) {
this.buff = buff;
} @Override
public void run() { buff.read();//这里估计会一直阻塞 System.out.println("读结束"); }
}
package cn.vicky.chapt10;

import java.util.concurrent.locks.ReentrantLock;

/**
*
* @author Vicky.H
*/
public class BufferInterruptibly { private ReentrantLock lock = new ReentrantLock(); public void write() {
lock.lock();
try {
long startTime = System.currentTimeMillis();
System.out.println("开始往这个buff写入数据…");
for (;;)// 模拟要处理很长时间
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE) {
break;
}
}
System.out.println("终于写完了");
} finally {
lock.unlock();
}
} public void read() throws InterruptedException {
lock.lockInterruptibly();// 注意这里,可以响应中断
try {
System.out.println("从这个buff读数据");
} finally {
lock.unlock();
}
} public static void main(String args[]) {
BufferInterruptibly buff = new BufferInterruptibly(); final Writer2 writer = new Writer2(buff);
final Reader2 reader = new Reader2(buff); writer.start();
reader.start(); new Thread(new Runnable() { @Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
if (System.currentTimeMillis()
- start > 5000) {
System.out.println("不等了,尝试中断");
reader.interrupt();
break;
}
}
}
}).start(); }
} class Reader2 extends Thread { private BufferInterruptibly buff; public Reader2(BufferInterruptibly buff) {
this.buff = buff;
} @Override
public void run() { try {
buff.read();//可以收到中断的异常,从而有效退出
} catch (InterruptedException e) {
System.out.println("我不读了");
} System.out.println("读结束"); }
} class Writer2 extends Thread { private BufferInterruptibly buff; public Writer2(BufferInterruptibly buff) {
this.buff = buff;
} @Override
public void run() {
buff.write();
} }

2个程序,运行结果:

run:
开始往这个buff写入数据…
不等了,尝试中断

run:
开始往这个buff写入数据…
不等了,尝试中断
我不读了
读结束

‍ReentrantLock是一个互斥的同步器,其实现了接口Lock,里面的功能函数主要有:
1. ‍lock() -- 阻塞模式获取资源
2. ‍lockInterruptibly() -- 可中断模式获取资源
3. ‍tryLock() -- 尝试获取资源
4. tryLock(time) -- 在一段时间内尝试获取资源
5. ‍unlock() -- 释放资源

ReentrantLock实现Lock有两种模式即公平模式和不公平模式
Concurrent包下的同步器都是基于AQS框架,在ReentrantLock里面会看到这样三个类
-----------------------------------------------------------------------
static abstract class Sync extends AbstractQueuedSynchronizer {
    abstract void lock();
    final boolean nonfairTryAcquire(int acquires) { ... }
    protected final boolean tryRelease(int releases) { ... }
}
-----------------------------------------------------------------------
final static class NonfairSync extends Sync {
    protected final boolean tryAcquire(int acquires) { ... }
    final void lock() { ... }
}
-----------------------------------------------------------------------
final static class FairSync extends Sync {
    final void lock() { ... }
    protected final boolean tryAcquire(int acquires) { ... }
}
-----------------------------------------------------------------------
再回归到ReentrantLock对Lock的实现上
0. ‍ReentrantLock实例化
   ReentrantLock有个属性sync,实际上对Lock接口的实现都是包装了一下这个sync的实现
   如果是公平模式则创建一个FairSync对象,否则创建一个NonfairSync对象,默认是不公平模式
1. lock() 调用sync.lock()
   公平模式下:直接走AQS的acquire函数,此函数的逻辑走一次tryAcquire,如果成功
   线程拜托同步器的控制,否则加入NODE链表,进入acquireQueued的tryAcquire,休眠,被唤醒的轮回
   不公平模式下和公平模式下逻辑大体上是一样的,不同点有两个:
   a. 在执行tryAcquire之前的操作,不公平模式会直接compareAndSetState(0, 1)原子性的设置AQS的资源
   0表示目前没有线程占据资源,则直接抢占资源,不管AQS的NODE链表的FIFO原则
   b. tryAcquire的原理不一样,不公平模式的tryAcquire只看compareAndSetState(0, 1)能否成功
   而公平模式还会加一个条件就是此线程对于的NODE是不是NODE链表的第一个
   c. 由于tryAcquire的实现不一样,而公平模式和不公平模式在lock期间走的逻辑是一样的(AQS的acquireQueued的逻辑)
   d. 对于一个线程在获取到资源后再调用lock会导致AQS的资源做累加操作,同理线程要彻底的释放资源就必须同样
   次数的调用unlock来做对应的累减操作,因为对应ReentrantLock来说tryAcquire成功一个必须的条件就是compareAndSetState(0, 1)
   e. 由于acquireQueued过程中屏蔽了线程中断,只是在线程拜托同步器控制后,如果记录线程在此期间被中断过则标记线程的
   中断状态
2. ‍lockInterruptibly() 调用sync.acquireInterruptibly(1),上一篇文章讲过AQS的核心函数,这个过程和acquireQueued
   是一样的,只不过在阻塞期间如果被标记中断则线程在park期间被唤醒,然后直接退出那个轮回,抛出中断异常
   由于公平模式和不公平模式下对tryAcquire的实现不一样导致‍lockInterruptibly逻辑也是不一样
3. tryLock() 函数只是尝试性的去获取一下锁,跟tryAcquire一样,这两种模式下走的代码一样都是公平模式下的代码
4. tryLock(time) 调用sync.tryAcquireNanos(time),上一篇文章讲过AQS的核心函数,这个过程和acquireQueued一样,
   a. 在阻塞前会先计算阻塞的时间,进入休眠
   b. 如果被中断则会判断时间是否到了
      1. 如果没到则且被其他线程设置了中断标志,退出那个轮回,抛出中断异常,如果没有被设置中断标记则是前一个线程
      释放了资源再唤醒了它,其继续走那个轮回,轮回中,如果tryAcquire成功则摆脱了同步器的控制,否则回到a
      2. 如果时间到了则退出轮回,获取资源失败
5. ‍unlock() 调用sync.release(1),上一篇文章讲过AQS的核心函数,release函数会调用Sync实现的tryRelease函数来判断
   释放资源是否成功,即Sync.tryRelease函数,其逻辑过程是
   a. 首先判断目前占据资源的线程是不是调用者,如果不是会抛出异常IllegalMonitorStateException
   b. 如果是则进行AQS资源的减1逻辑,如果再减1后AQS资源变成0则表示调用线程测得放弃了此锁,返回给release的值的TRUE,
   release会唤醒下一个线程
-----------------------------------------------------------------------
整体来看ReentrantLock互斥锁的实现大致是
1. 自己实现AQS的tryAcquire和tryRelease逻辑,tryAcquire表示尝试去获取锁,tryRelease表示尝试去释放锁
2. ReentrantLock对lock(),trylock(),trylock(time),unlock()的实现都是使用AQS的框架,然后AQS的框架又返回调用
ReentrantLock实现的tryAcquire和tryRelease来对线程是否获取锁和释放锁成功做出依据判断

ReentRantLock使用的更多相关文章

  1. Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)

    AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...

  2. 架构师养成记--14.重入锁ReentrantLock 和 读写锁 ReentrantReadWriteLock

    ReentrantLock 有嗅探锁定和多路分支等功能,其实就是synchronized,wait,notify的升级. this锁定当前对象不方便,于是就有了用new Object()来作为锁的解决 ...

  3. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析

    前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...

  4. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之二lock方法分析

    前一篇博客简单介绍了ReentrantLock的定义和与synchronized的区别,下面跟随LZ的笔记来扒扒ReentrantLock的lock方法.我们知道ReentrantLock有公平锁.非 ...

  5. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介

    注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...

  6. java线程 公平锁 ReentrantLock(boolean fair)

    一.公平锁 1.为什么有公平锁 CPU在调度线程的时候是在等待队列里随机挑选一个线程,由于这种随机性所以是无法保证线程先到先得的(synchronized控制的锁就是这种非公平锁).但这样就会产生饥饿 ...

  7. Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

    本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...

  8. 【JUC】JDK1.8源码分析之ReentrantLock(三)

    一.前言 在分析了AbstractQueuedSynchronier源码后,接着分析ReentrantLock源码,其实在AbstractQueuedSynchronizer的分析中,已经提到过Ree ...

  9. Lock、ReentrantLock、synchronized、ReentrantReadWriteLock使用

    先来看一段代码,实现如下打印效果: 1 2 A 3 4 B 5 6 C 7 8 D 9 10 E 11 12 F 13 14 G 15 16 H 17 18 I 19 20 J 21 22 K 23 ...

  10. java分析源码-ReentrantLock

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

随机推荐

  1. chomre常用快捷键

    Ctrl+T                                打开新标签页. 按住 Ctrl 键的同时点击链接.或用鼠标中键(或鼠标滚轮)点击链接.    从后台在新标签页中打开链接. ...

  2. dns服务

    http://33024.blog.163.com/blog/static/12307042220119179237568/

  3. override与overload的区别

    override(重写,覆盖) 1.方法名.参数.返回值相同. 2.子类方法不能缩小父类方法的访问权限. 3.子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常). 4.存在于父类和子类之 ...

  4. linux的mount(挂载)命令

    前言: 1.挂载点必须是一个目录. 2.一个分区挂载在一个已存在的目录上,这个目录可以不为空,但挂载后这个目录下以前的内容将不可用. 对于其他操作系统建立的文件系统的挂载也是这样.但是需要理解的是:光 ...

  5. 千万级SQL Server数据库表分区的实现

    千万级SQL Server数据库表分区的实现 2010-09-10 13:37 佚名 数据库 字号:T | T 一般在千万级的数据压力下,分区是一种比较好的提升性能方法.本文将介绍SQL Server ...

  6. python_os

    1. 基本功能的介绍 os模块包含普通的操作系统的功能 2. 常用的变量 (1)os.name 获取正在使用的平台, Windows 返回 nt, Linux或者Unix 返回 posix 3. 常用 ...

  7. JS对象之间的关系

    JS对象类型 JS中,可以将对象分为"内部对象"."宿主对象"和"自定义对象"三种. 1.本地对象 ECMA-262定义为"独立于 ...

  8. 不错的开源FTP类库

    socket开源ftp类库代码:http://netftp.codeplex.com/ 需要注意事项,如果以下代码出现乱码问题,可以设置其中的Encoding属性就可以. 用法示例: using Sy ...

  9. 20145320 《Java程序设计》第1周学习总结

    20145320 <Java程序设计>第1周学习总结 教材学习内容总结 第一章 Java最早是Sun公司的项目Green Project中编写Star7应用程序的程序语言,1995年5月2 ...

  10. MVC 移除复数表名的契约

    在数据库上下文中添加: using System.Data.Entity.ModelConfiguration.Conventions; protected override void OnModel ...