Java 5 引入的 Concurrent 并发库软件包中,提供了 ReentrantLock 可重入同步锁,用来替代 synchronized 关键字原语,并可提供更好的性能,以及更强大的功能。使用方法也很简单:

public final ReentrantLock lock=new ReentrantLock();

......

try {

lock.lock();

// 进入同步内容

....

} finally {

lock.unlock(); // 必须在 finally 块中解锁,否则一旦出现异常,执行不到解锁,则一直锁住了。

}

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原语来加锁,如下:

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读数据");    
        }    
    }    
}

接着,我们来定义2个线程,一个线程去写,一个线程去读。

public class Writer extends Thread {    
     
    private Buffer buff;    
     
    public Writer(Buffer buff) {    
        this.buff = buff;    
    }    
     
    @Override    
    public void run() {    
        buff.write();    
    }    
     
}    
     
public class Reader extends Thread {    
     
    private Buffer buff;    
     
    public Reader(Buffer buff) {    
        this.buff = buff;    
    }    
     
    @Override    
    public void run() {    
     
        buff.read();//这里估计会一直阻塞    
     
        System.out.println("读结束");    
     
    }    
     
}

好了,写一个Main来试验下,我们有意先去“写”,然后让“读”等待,“写”的时间是无穷的,就看“读”能不能放弃了。

public class Test {    
    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吧,可中断缓存。

import java.util.concurrent.locks.ReentrantLock;    
     
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();    
        }    
    }    
     
}

当然,要对reader和writer做响应的修改

public class Reader extends Thread {    
     
    private BufferInterruptibly buff;    
     
    public Reader(BufferInterruptibly buff) {    
        this.buff = buff;    
    }    
     
    @Override    
    public void run() {    
     
        try {    
            buff.read();//可以收到中断的异常,从而有效退出    
        } catch (InterruptedException e) {    
            System.out.println("我不读了");    
        }    
           
        System.out.println("读结束");    
     
    }    
     
}    
     
    
public class Writer extends Thread {    
     
    private BufferInterruptibly buff;    
     
    public Writer(BufferInterruptibly buff) {    
        this.buff = buff;    
    }    
     
    @Override    
    public void run() {    
        buff.write();    
    }    
     
}    
     
public class Test {    
    public static void main(String[] args) {    
        BufferInterruptibly buff = new BufferInterruptibly();    
     
        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 (;;) {    
                    if (System.currentTimeMillis()    
                            - start > 5000) {    
                        System.out.println("不等了,尝试中断");    
                        reader.interrupt();    
                        break;    
                    }    
     
                }    
     
            }    
        }).start();    
     
    }    
}

这次“读”线程接收到了lock.lockInterruptibly()中断,并且有效处理了这个“异常”。

Java 并发编程中使用 ReentrantLock 替代 synchronized 关键字原语的更多相关文章

  1. Java并发编程中的设计模式解析(二)一个单例的七种写法

    Java单例模式是最常见的设计模式之一,广泛应用于各种框架.中间件和应用开发中.单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程.类的加载等知识,系统地介绍一下单例模 ...

  2. Java并发编程中的若干核心技术,向高手进阶!

    来源:http://www.jianshu.com/p/5f499f8212e7 引言 本文试图从一个更高的视角来总结Java语言中的并发编程内容,希望阅读完本文之后,可以收获一些内容,至少应该知道在 ...

  3. Java并发编程中的相关注解

    引自:http://www.cnblogs.com/phoebus0501/archive/2011/02/21/1960077.html Java并发编程中,用到了一些专门为并发编程准备的 Anno ...

  4. Java并发编程知识点总结Volatile、Synchronized、Lock实现原理

    Volatile关键字及其实现原理 在多线程并发编程中,Volatile可以理解为轻量级的Synchronized,用volatile关键字声明的变量,叫做共享变量,其保证了变量的“可见性”以及“有序 ...

  5. Java并发编程中的设计模式解析(一)

    Java并发编程,除了被用于各种Web应用.分布式系统和大数据系统,构成高并发系统的核心基础外,其本身也蕴含着大量的设计模式思想在里面.这一系列文章主要是结合Java源码,对并发编程中使用到的.实现的 ...

  6. 【Java并发编程之深入理解】Synchronized的使用

    原文:https://blog.csdn.net/zjy15203167987/article/details/82531772 1.为什么要使用synchronized 在并发编程中存在线程安全问题 ...

  7. 转:【Java并发编程】之七:使用synchronized获取互斥锁的几点说明

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17199201     在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访 ...

  8. 【Java并发编程】之七:使用synchronized获取互斥锁的几点说明

    在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访问对象并要求操作相同资源时,分割了原子操作就有可能出现数据的不一致或数据不完整的情况,为避免这种情况的发生,我们会采取同步机制,以确 ...

  9. Java并发编程实战3-可见性与volatile关键字

    1. 缓存一致性问题 在计算机中,每条指令都是在CPU执行的,而CPU又不具备存储数据的功能,因此数据都是存储在主存(即内存)和外存(硬盘)中.但是,主存中数据的存取速度高于外存中数据的存取速度(这也 ...

随机推荐

  1. 使用js将后台返回的数据转换成树形结构

    将类似如下数据转换成树形的数据: [ { id: 1, name: '1', }, { id: 2, name: '1-1', parentId: 1 }, { id: 3, name: '1-1-1 ...

  2. 005 String s = "Hello";s = s + " world!";执行这两行代码执行后,原始的 String 对象中的内容到底变了没有?

    原始的String对象中的内容没有改变成“Hello world”. 1.原因 因为在Java中String类被设计成不可改变的类,所以String类的所有对象都是不可变的.第一句代码中,s(存储在栈 ...

  3. python已安装好第三方库,pycharm import时仍标红的解决办法

    pip install pymysql之后导入import pymysql时候标红 发现 pymysql下方还是标红,不能正常导入 可以试用一下以下的办法 解决办法: 首先打开 Settings找到P ...

  4. Bootstrap 响应式表格

    响应式表格 通过把任意的 .table 包在 .table-responsive class 内,您可以让表格水平滚动以适应小型设备(小于 768px).当在大于 768px 宽的大型设备上查看时,您 ...

  5. Ukulele 常用和弦

  6. Clover KextsToPatch 使用方法 2015.10.21

    Clover KextsToPatch 使用方法 2015.10.21   前些天,因为 Thinkpad X230 BIOS 白名单限制,给她换了一块 ar9285 无线网卡,只是因为这块网卡正好可 ...

  7. UVa 291 The House Of Santa Claus——回溯dfs

    题意:从左下方的1开始,一笔画出圣诞老人的房子. #include <iostream> #include <cstring> using namespace std; ][] ...

  8. PHP获取文件后缀的7中方法

    在日常的工作当中我们避免不了要经常获取文件的后缀名,今天我就整理了一下7种获取文件后缀的方法,希望对大家有所帮助. $url = 'http://www.baidu.com/uploads/20185 ...

  9. 搭建pip源

    1.安装pip软件 yum -y install python-pippip install --upgrade pippip install pip2pi 2.安装apacheyum -y inst ...

  10. css3的border-radius属性使用方法

    1.border-radius可以包含两个参数值,第一个水平圆角半径,第二个为垂直半径,并且两个参数值用“/”分开. 2.border-radius:设置一个值为四个角都相同,两个值为左上和右下相同, ...