在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问。本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。

也许有朋友会问,既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?这个问题将在下面进行阐述。本文先从synchronized的缺陷讲起,然后再讲述java.util.concurrent.locks包下常用的有哪些类和接口,最后讨论以下一些关于锁的概念方面的东西。

以下是本文目录大纲:

一.synchronized的缺陷

二.java.util.concurrent.locks包下常用的类

三.锁的相关概念介绍

若有不正之处请多多谅解,并欢迎批评指正。

一.synchronized的缺陷

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?

在上面一篇文章中,我们了解到如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

2)线程执行发生异常,此时JVM会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

但是采用synchronized关键字来实现同步的话,就会导致一个问题:

如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。

总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:

1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

二.java.util.concurrent.locks包下常用的类

下面我们就来探讨一下java.util.concurrent.locks包中常用的类和接口。

1.Lock

首先要说明的就是Lock,通过查看Lock的源码可知,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()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。newCondition()这个方法暂且不在此讲述,会在后面的线程协作一文中讲述。

在Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?

首先lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。

由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock = ...;

lock.lock();

try{

//处理任务

}catch(Exception ex){

}finally{

lock.unlock();   //释放锁

}

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()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过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()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。

因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。

而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

2.ReentrantLock

ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例看具体看一下如何使用ReentrantLock。

例子1,lock()的正确使用方法

public class Test {

private ArrayList<Integer> arrayList = new ArrayList<Integer>();

public static void main(String[] args)  {

final Test test = new Test();

new Thread(){

public void run() {

test.insert(Thread.currentThread());

};

}.start();

new Thread(){

public void run() {

test.insert(Thread.currentThread());

};

}.start();

}

public void insert(Thread thread) {

Lock lock = new ReentrantLock();    //注意这个地方

lock.lock();

try {

System.out.println(thread.getName()+"得到了锁");

for(int i=0;i<5;i++) {

arrayList.add(i);

}

} catch (Exception e) {

// TODO: handle exception

}finally {

System.out.println(thread.getName()+"释放了锁");

lock.unlock();

}

}

}

各位朋友先想一下这段代码的输出结果是什么?

Thread-0得到了锁

Thread-1得到了锁

Thread-0释放了锁

Thread-1释放了锁

也许有朋友会问,怎么会输出这个结果?第二个线程怎么会在第一个线程释放锁之前得到了锁?原因在于,在insert方法中的lock变量是局部变量,每个线程执行该方法时都会保存一个副本,那么理所当然每个线程执行到lock.lock()处获取的是不同的锁,所以就不会发生冲突。

知道了原因改起来就比较容易了,只需要将lock声明为类的属性即可。

public class Test {

private ArrayList<Integer> arrayList = new ArrayList<Integer>();

private Lock lock = new ReentrantLock();    //注意这个地方

public static void main(String[] args)  {

final Test test = new Test();

new Thread(){

public void run() {

test.insert(Thread.currentThread());

};

}.start();

new Thread(){

public void run() {

test.insert(Thread.currentThread());

};

}.start();

}

public void insert(Thread thread) {

lock.lock();

try {

System.out.println(thread.getName()+"得到了锁");

for(int i=0;i<5;i++) {

arrayList.add(i);

}

} catch (Exception e) {

// TODO: handle exception

}finally {

System.out.println(thread.getName()+"释放了锁");

lock.unlock();

}

}

}

这样就是正确地使用Lock的方法了。

例子2,tryLock()的使用方法

public class Test {

private ArrayList<Integer> arrayList = new ArrayList<Integer>();

private Lock lock = new ReentrantLock();    //注意这个地方

public static void main(String[] args)  {

final Test test = new Test();

new Thread(){

public void run() {

test.insert(Thread.currentThread());

};

}.start();

new Thread(){

public void run() {

test.insert(Thread.currentThread());

};

}.start();

}

public void insert(Thread thread) {

if(lock.tryLock()) {

try {

System.out.println(thread.getName()+"得到了锁");

for(int i=0;i<5;i++) {

arrayList.add(i);

}

} catch (Exception e) {

// TODO: handle exception

}finally {

System.out.println(thread.getName()+"释放了锁");

lock.unlock();

}

} else {

System.out.println(thread.getName()+"获取锁失败");

}

}

}

输出结果:

Thread-0得到了锁

Thread-1获取锁失败

Thread-0释放了锁

例子3,lockInterruptibly()响应中断的使用方法:

public class Test {

private Lock lock = new ReentrantLock();

public static void main(String[] args)  {

Test test = new Test();

MyThread thread1 = new MyThread(test);

MyThread thread2 = new MyThread(test);

thread1.start();

thread2.start();

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

thread2.interrupt();

}

public void insert(Thread thread) throws InterruptedException{

lock.lockInterruptibly();   //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出

try {

System.out.println(thread.getName()+"得到了锁");

long startTime = System.currentTimeMillis();

for(    ;     ;) {

if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)

break;

//插入数据

}

}

finally {

System.out.println(Thread.currentThread().getName()+"执行finally");

lock.unlock();

System.out.println(thread.getName()+"释放了锁");

}

}

}

class MyThread extends Thread {

private Test test = null;

public MyThread(Test test) {

this.test = test;

}

@Override

public void run() {

try {

test.insert(Thread.currentThread());

} catch (InterruptedException e) {

System.out.println(Thread.currentThread().getName()+"被中断");

}

}

}

运行之后,发现thread2能够被正确中断。

3.ReadWriteLock

ReadWriteLock也是一个接口,在它里面只定义了两个方法:

public interface ReadWriteLock {

/**

* Returns the lock used for reading.

*

* @return the lock used for reading.

*/

Lock readLock();

/**

* Returns the lock used for writing.

*

* @return the lock used for writing.

*/

Lock writeLock();

}

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

4.ReentrantReadWriteLock

ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。

下面通过几个例子来看一下ReentrantReadWriteLock具体用法。

假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:

public class Test {

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

public static void main(String[] args)  {

final Test test = new Test();

new Thread(){

public void run() {

test.get(Thread.currentThread());

};

}.start();

new Thread(){

public void run() {

test.get(Thread.currentThread());

};

}.start();

}

public synchronized void get(Thread thread) {

long start = System.currentTimeMillis();

while(System.currentTimeMillis() - start <= 1) {

System.out.println(thread.getName()+"正在进行读操作");

}

System.out.println(thread.getName()+"读操作完毕");

}

}

这段程序的输出结果会是,直到thread1执行完读操作之后,才会打印thread2执行读操作的信息。

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0读操作完毕

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1读操作完毕

而改成用读写锁的话:

public class Test {

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

public static void main(String[] args)  {

final Test test = new Test();

new Thread(){

public void run() {

test.get(Thread.currentThread());

};

}.start();

new Thread(){

public void run() {

test.get(Thread.currentThread());

};

}.start();

}

public void get(Thread thread) {

rwl.readLock().lock();

try {

long start = System.currentTimeMillis();

while(System.currentTimeMillis() - start <= 1) {

System.out.println(thread.getName()+"正在进行读操作");

}

System.out.println(thread.getName()+"读操作完毕");

} finally {

rwl.readLock().unlock();

}

}

}

此时打印的结果为:

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0正在进行读操作

Thread-1正在进行读操作

Thread-0读操作完毕

Thread-1读操作完毕

说明thread1和thread2在同时进行读操作。

这样就大大提升了读操作的效率。

不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。

如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

关于ReentrantReadWriteLock类中的其他方法感兴趣的朋友可以自行查阅API文档。

5.Lock和synchronized的选择

总结来说,Lock和synchronized有以下几点不同:

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

Java并发编程:Lock(上)的更多相关文章

  1. Java并发编程:Lock

    Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.l ...

  2. java 并发编程lock使用详解

    浅谈Synchronized: synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其 ...

  3. java并发编程-StampedLock高性能读写锁

    目录 一.读写锁 二.悲观读锁 三.乐观读 欢迎关注我的博客,更多精品知识合集 一.读写锁 在我的<java并发编程>上一篇文章中为大家介绍了<ReentrantLock读写锁> ...

  4. 【多线程】Java并发编程:Lock(转载)

    原文链接:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...

  5. 转: 【Java并发编程】之二十:并发新特性—Lock锁和条件变量(含代码)

    简单使用Lock锁 Java5中引入了新的锁机制--Java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作.Lock接 ...

  6. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  7. [转载] java并发编程:Lock(线程锁)

    作者:海子 原文链接: http://www.cnblogs.com/dolphin0520/p/3923167.html 出处:http://www.cnblogs.com/dolphin0520/ ...

  8. Java并发编程:Lock(转)

    本文转自:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...

  9. 5、Java并发编程:Lock

    Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.l ...

  10. Java并发编程(06):Lock机制下API用法详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.Lock体系结构 1.基础接口简介 Lock加锁相关结构中涉及两个使用广泛的基础API:ReentrantLock类和Condition接 ...

随机推荐

  1. 转: DH密钥交换和ECDH原理

    转自:http://www.tuicool.com/articles/em6zEb DH密钥交换和ECDH原理 时间 2013-06-24 18:50:55  CSDN博客 原文  http://bl ...

  2. CSS制作一个简单网页的下拉导航栏

    网页下拉导航栏的制作 网页下拉导航栏的制作很简单,只需要运用好CSS中伪选择器. 首先说明几个简单的伪选择器(比较常用的): link:连接平常的状态 visited:连接被访问过之后 hover:鼠 ...

  3. [CAMCOCO][C#]我的系统架构.服务器端.(四)----Model层 实体的自我验证

    这是Model的第二篇,上一篇点这里 这块完全是扒了@何镇汐大神博客里的教程实现的,在这之前完全没想到数据验证居然可以这样做!!在此表示严重感谢!!! 点击这里可以去了解这个方法的原理,老胡估计自己是 ...

  4. Part 10 AngularJS sort rows by table header

    Here is what we want to do 1. The data should be sorted when the table column header is clicked 2. T ...

  5. Linux 系统中用户切换(su user与 su - user 的区别)

    1. Linux系统中用户切换的命令为su,语法为: su [-fmp] [-c command] [-s shell] [--help] [--version] [-] [USER [ARG]] 参 ...

  6. 2015英特尔® 实感™ (Intel® RealSense™) 动手开发实验课

    2015年英特尔® 全球实感技术动手实验课路演来到中国, 这次在中国将有北京和广州两站,包括一天的动手实验室活动 - 面向对感知计算.3D 开发和虚拟现实兴趣浓厚的开发人员.英特尔专家将会指导您如何借 ...

  7. 微软SQLHelper.cs类 中文版

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Co ...

  8. iOS开发中遇到的头文件找不到的问题解决办法

    有时会遇到莫名其妙的明明有这个文件,但是就是显示头文件找不到,我也是咨询了技术大牛之后知道的这个方法,之后恰巧我一个朋友问我cocoapod加进去之后头文件找不到,我就让他试了下这个方法果然好用,我也 ...

  9. SQL语句统计每天的数据

    按用户注册时间统计每天注册的不同来源.不同状态的用户数量: ), RegisterTime, ) RDate ,--DATEPART(YEAR, RegisterTime) 年 ) END 'AWai ...

  10. python基础:三元运算

    学习条件运算时,对于简单的 if else 语句,可以使用三元运算来表示,即: 1 2 3 4 5 6 7 8 # 普通条件语句 if 1 == 1:     name = 'wupeiqi' els ...