文档章节

Java中locks与synchronized及其异同

乐在克里特
 乐在克里特
发布于 2017/02/23 13:43
字数 2417
阅读 2
收藏 0

关键字:synchronized、java.util.concurrent.locks.Lock、同步、并发、锁

一、【引言】

JDK1.5之前,实现同步主要是使用synchronized,而在JDK1.5中新增了java.util.concurrent包及其两个子包locks和atomic,其中子包locks中定义了系列关于锁的抽象的类。本文主要介绍java.util.concurrent.locks的使用及其与synchronized两种方式实现同步的异同。

二、【synchronized同步】

synchronized相信很多熟悉J2SE的人都不会对这个关键字陌生,它用于实现多个线程之间的同步,一般有两种使用方式:
1、在方法上加synchronized关键字

  1. public synchronized void f() {  
  2. //do something  
  3. }  

2、synchronized同步代码块

  1. synchronized (mutex) {  
  2. // do something  
  3. }  

对于这两种方式又应该着重区分是否为“static”的,因为static的成员是属于类的,非staitc的是属于具体实例的,所以在使用synchronized时应该注意方法或选择的同步变量是否为static的,如下代码:

  1. package test.lock;  
  2.   
  3. /** 
  4.  * @author whwang 
  5.  * 2012-1-10 下午11:19:04 
  6.  */  
  7. public class SyncTest {  
  8.       
  9.     private Object mutex = new Object();  
  10.       
  11.     public synchronized void f1() {  
  12.         synchronized (mutex) {  
  13.             System.err.println("nonstatic method f1....");  
  14.             try {  
  15.                 Thread.sleep(2 * 1000);  
  16.             } catch (InterruptedException e) {  
  17.                 e.printStackTrace();  
  18.             }  
  19.         }  
  20.     }  
  21.       
  22.     public static void main(String[] args) {  
  23.         SycnThread thread1 = new SycnThread(new SyncTest());  
  24.         SycnThread thread2 = new SycnThread(new SyncTest());  
  25.         SycnThread thread3 = new SycnThread(new SyncTest());  
  26.         thread1.start();  
  27.         thread2.start();  
  28.         thread3.start();  
  29.     }  
  30.       
  31. }  
  32.   
  33. class SycnThread extends Thread {  
  34.       
  35.     private SyncTest st;  
  36.       
  37.     public SycnThread(SyncTest st) {  
  38.         this.st = st;  
  39.     }  
  40.       
  41.     @Override  
  42.     public void run() {  
  43.         while (true) {  
  44.             st.f1();  
  45.         }  
  46.     }  
  47. }  


在main方法,创建thread1、2、3三个线程,它们都调用SyncTest的f1()方法,而方法f1()使用mutex(非static)来做同步变量,如果你的意图是实现这3个线程对方法f1的同步,那么运行的结果会让你大失所望,因为这样根本就无法使得这3个线程同步,原因在于:mutex是一个非static的成员变量,也就是说每new SyncTest(),它们的mutex变量都是不相同的。这样,对于上面这个程序来说,意味着每一个线程都使用了一个mutex来做它们各自的不同变量,如果希望上面3个线程同步,可以把mutex改为static或在创建SycnThread时,传入的SyncTest都为同一个对象即可。
还有当使用String常量或全局变量时都应该引起注意,Java线程同步小陷阱,你掉进去过吗?

三、【java.util.concurrent.locks下的锁实现同步】

自JDK1.5以为,Java提供了java.util.concurrent这个并发包,在它的子包locks中,提供了一系列关于锁的抽象的类。主要有两种锁ReentrantLockReentrantReadWriteLock,而其他的两个类,都是“辅助”类,如AbstractQueuedSynchronizer就是一个用于实现特殊规则锁的抽象类,ReentrantLock和ReentrantReadWriteLock内部都有一个继承了该抽象类的内部类,用于实现特定锁的功能。下文主要介绍:ReentrantLock和ReentrantReadWriteLock

1、可重入的锁ReentrantLock

使用ReentrantLock锁最简单的一个例子:

  1. Lock lock = new ReentrantLock();  
  2. try {  
  3.     lock.lcok();  
  4.     // do something  
  5. finally {  
  6.     lock.unlock();  
  7. }  

 

上面这段代码,首先创建了一个lock,然后调用它的lock()方法,开启锁定,在最后调用它的unlock()解除锁定。值得注意的时,一般在使用锁时,都应该按上面的风格书写代码,即lock.unlock()最好放在finally块,这样可以防止,执行do something时发生异常后,导致锁永远无法被释放。

到此,还没发现Lock与synchronized有什么不同,Lock与synchronized不同之处主要体现在Lock比synchronized更灵活得多,而这些灵活又主要体现在如下的几个方法:

//lock()
tryLock()
tryLock(long timeout, TimeUnit timeUnit)
lockInterruptibly()

//unlock()

A、trylock()方法:如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
B、tryLock(long timeout, TimeUnit timeUnit)方法:如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
是不是比synchronized灵活就体现出来了,打个不是很恰当的比分:你现在正在忙于工作,突然感觉内急,于是你跑向洗手间,到门口发现一个“清洁中,暂停使用”的牌牌。没办法,工作又忙,所以你只好先放弃去洗手间回去忙工作,可能如此反复,终于你发现可以进了,于是......
像这样的场景用synchronized你怎么实现?没办法,如果synchronized,当你发现洗手间无法暂时无法进入时,就只能乖乖在门口干等了。而使用trylock()呢,首先你试着去洗手间,发现暂时无法进入(trylock返回false),于是你继续忙你的工作,如此反复,直到可以进入洗手间为止(trylock返回true)。甚至,你非常急,你可以尝试性的在门口等20秒,不行再去忙工作(trylock(20, TimeUnit.SECONDS);)。
C、lockInterruptibly()方法
lockInterruptibly()方法的执行如下:

如果该锁定没有被另一个线程保持,则获取该锁定并立即返回,将锁定的保持计数设置为 1。
如果当前线程已经保持此锁定,则将保持计数加 1,并且该方法立即返回。
如果锁定被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态:
    a、锁定由当前线程获得;
    b、或者其他某个线程中断当前线程。 
如果当前线程获得该锁定,则将锁定保持计数设置为1。
如果当前线程:
    a、在进入此方法时已经设置了该线程的中断状态;
    b、或者在等待获取锁定的同时被中断。 
则抛出 InterruptedException,并且清除当前线程的已中断状态。
即lockInterruptibly()方法允许在等待时由其它线程调用它的Thread.interrupt方法来中断等待而直接返回,这时不再获取锁,而会抛出一个InterruptedException。
D、lockInterruptibly()方法源码介绍

  1. public void lockInterruptibly() throws InterruptedException {  
  2.     sync.acquireInterruptibly(1);  
  3. }  


a、首先lockInterruptibly调用了内部类sync的acquireInterruptibly(1)方法,这个sync就是前面提到的AbstractQueuedSynchronizer的子类

  1. abstract static class Sync extends AbstractQueuedSynchronizer {  
  2.     // ....  
  3. }  
  1. public final void acquireInterruptibly(int arg)  
  2.             throws InterruptedException {  
  3.     if (Thread.interrupted())  
  4.         throw new InterruptedException();  
  5.     if (!tryAcquire(arg))  
  6.         doAcquireInterruptibly(arg);  
  7. }  


b、在sync的acquireInterruptibly方法中,首先检查当前现场是否已经中断,如果已经中断,抛出InterruptedException异常,否则调用调用sync的doAcquireInterruptibly方法。

  1. private void doAcquireInterruptibly(int arg)  
  2.         throws InterruptedException {  
  3.         final Node node = addWaiter(Node.EXCLUSIVE);  
  4.         boolean failed = true;  
  5.         try {  
  6.             for (;;) {  
  7.                 final Node p = node.predecessor();  
  8.                 if (p == head && tryAcquire(arg)) {  
  9.                     setHead(node);  
  10.                     p.next = null// help GC  
  11.                     failed = false;  
  12.                     return;  
  13.                 }  
  14.         // 抛出InterruptedException异常  
  15.                 if (shouldParkAfterFailedAcquire(p, node) &&  
  16.                     parkAndCheckInterrupt())  
  17.                     throw new InterruptedException();  
  18.             }  
  19.         } finally {  
  20.             if (failed)  
  21.                 cancelAcquire(node);  
  22.         }  
  23.     }  


c、在sync的方法doAcquireInterruptibly中,关键在于检测到中断则直接退出循环(不在等待获取锁),而是直接抛出InterruptedException异常,最后在finally里调用cancelAcquire取消获锁操作。
E、除了这些方法之外,ReentrantLock还提供了很多实用的方法,这里就不再一一讲述
对于Lock,还有一个特别值得注意的地方,请看下面的代码:

  1. Lock lock = new ReentrantLock();  
  2. // ....  
  3. try {  
  4.     lock.lock();  
  5.     lock.lock();  
  6.     // do something...  
  7. finally {  
  8.     lock.unlock();  
  9. }  
  10. // ....  


可以看到上面,上面代码调用lock()方法和调用unlock()方法的次数不同,这样的一段代码执行完后,别的线程是否已经可以获取该lock锁呢?

  1. package test.mult;  
  2.   
  3. import java.util.concurrent.locks.Lock;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5.   
  6. /** 
  7.  * @ClassName : Test 
  8.  * @author whwang 
  9.  * @date 2012-1-11 下午02:04:04 
  10.  */  
  11. public class Test {  
  12.   
  13.     private String name;  
  14.       
  15.     public Test(String name) {  
  16.         this.name = name;  
  17.     }  
  18.       
  19.     public static void main(String[] args) {  
  20.         // 这样创建一个"公平竞争"的锁  
  21.         Lock lock = new ReentrantLock(true);  
  22.         MyThread t1 = new MyThread(lock, new Test("test1"));  
  23.         MyThread t2 = new MyThread(lock, new Test("test2"));  
  24.         t1.start();  
  25.         t2.start();  
  26.     }  
  27.   
  28.     private static class MyThread extends Thread {  
  29.         Lock lock = null;  
  30.         Test test = null;  
  31.   
  32.         public MyThread(Lock lock, Test test) {  
  33.             this.lock = lock;  
  34.             this.test = test;  
  35.         }  
  36.   
  37.         @Override  
  38.         public void run() {  
  39.             while (true) {  
  40.                 try {  
  41.                     // 调用两次lock  
  42.                     lock.lock();  
  43.                     lock.lock();  
  44.                     System.err.println(test.name + " locked...");  
  45.                     try {  
  46.                         Thread.sleep(3 * 1000);  
  47.                     } catch (InterruptedException e) {  
  48.                         e.printStackTrace();  
  49.                     }  
  50.                 } finally {  
  51.                     lock.unlock();  
  52.                 }  
  53.             }  
  54.         }  
  55.   
  56.     }  
  57. }  


运行的结果:

  1. test1 locked...  
  2. test1 locked...  
  3. test1 locked...  
  4. test1 locked...  
  5. test1 locked...  
  6. test1 locked...  


永远都是持有test1这个类的线程才能获取锁,其实是第一个获取锁的线程,他永远都拿着锁不放。
所以在使用Lock的时候,lock与unlock一定要配对

2、可重入的读写锁ReentrantReadWriteLock

该锁的用法与ReentrantLock基本一样,只是ReentrantReadWriteLock实现了特殊规则(读写锁),在ReentrantReadWriteLock中有两个内部类ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock(实际上不止两个内部类,还有实现AbstractQueuedSynchronizer的Sync等等),这两个类分别可以使用ReentrantReadWriteLock的readLock()和writeLock()返回,该读写锁的规则是:只要没有writer,读取锁定可以由多个reader 线程同时保持,而写入锁定是独占的。下面通过一个简单的例子来了解它:

  1. package test.mult;  
  2.   
  3. import java.util.concurrent.locks.ReentrantReadWriteLock;  
  4.   
  5. /** 
  6. * @ClassName: ReadWriteLockTest 
  7. * @author whwang 
  8. * @date 2012-1-11 下午02:20:59 
  9.  */  
  10. public class ReadWriteLockTest {  
  11.   
  12.     static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);  
  13.   
  14.     public static void main(String[] args) {  
  15.         // 是否可以进入多个reader - 可以  
  16.   
  17.         // 是否可以进入多个writer - 不可以  
  18.   
  19.         // 当有reader进入后, writer是否可以进入 - 不可以  
  20.   
  21.         // 当有writer进入后, reader是否可以进入 - 不可以  
  22.   
  23.         MyThread t1 = new MyThread(0"t1");  
  24.         MyThread t2 = new MyThread(0"t2");  
  25.         MyThread t3 = new MyThread(1"t3");  
  26.         t1.start();  
  27.         t2.start();  
  28.         t3.start();  
  29.     }  
  30.   
  31.     private static class MyThread extends Thread {  
  32.   
  33.         private int type;  
  34.   
  35.         private String threadName;  
  36.   
  37.         public MyThread(int type, String threadName) {  
  38.             this.threadName = threadName;  
  39.             this.type = type;  
  40.         }  
  41.   
  42.         @Override  
  43.         public void run() {  
  44.             while (true) {  
  45.                 if (type == 0) {  
  46.                     // read  
  47.                     ReentrantReadWriteLock.ReadLock readLock = null;  
  48.                     try {  
  49.                         readLock = lock.readLock();  
  50.                         readLock.lock();  
  51.                         System.err.println("to read...." + threadName);  
  52.                         try {  
  53.                             Thread.sleep(5 * 1000);  
  54.                         } catch (InterruptedException e) {  
  55.                             e.printStackTrace();  
  56.                         }  
  57.                     } finally {  
  58.                         readLock.unlock();  
  59.                     }  
  60.                 } else {  
  61.                     // write  
  62.                     ReentrantReadWriteLock.WriteLock writeLock = null;  
  63.                     try {  
  64.                         writeLock = lock.writeLock();  
  65.                         writeLock.lock();  
  66.                         System.err.println("to write...." + threadName);  
  67.                         try {  
  68.                             Thread.sleep(5 * 1000);  
  69.                         } catch (InterruptedException e) {  
  70.                             e.printStackTrace();  
  71.                         }  
  72.                     } finally {  
  73.                         writeLock.unlock();  
  74.                     }  
  75.                 }  
  76.             }  
  77.         }  
  78.     }  
  79. }  


3、AbstractQueuedSynchronizer:如果需要自己实现一些特殊规则的锁,可以通过拓展该类来实现。

参考文档:
http://wenku.baidu.com/view/41480552f01dc281e53af090.html
http://tutorials.jenkov.com/java-concurrency/index.html

 

http://blog.csdn.net/smcwwh/article/details/7193666

为了更加理解,还可以看一下:

http://www.cnblogs.com/benshan/p/3551987.html

© 著作权归作者所有

共有 人打赏支持
乐在克里特
粉丝 16
博文 268
码字总数 394729
作品 0
杭州
程序员
私信 提问
JAVA程序员面试题整理(较全面)

以下是在面试中可能会遇到的问题,话不多说,往下看 1、面向对象的特征有哪些方面? 2、访问修饰符public,private,protected,以及不写(默认)时的区别? 3、String 是最基本的数据类型吗? ...

编程大侠
04/09
0
0
170道Java工程师面试题,你敢挑战吗?

1、面向对象的特征有哪些方面? 2、访问修饰符public,private,protected,以及不写(默认)时的区别? 3、String 是最基本的数据类型吗? 4、float f=3.4;是否正确? 5、short s1 = 1; s1 = ...

技术小能手
11/01
0
0
synchronized ReentrantLock 比较分析

     在编写多线程代码的时候,对于不允许并发的代码,很多需要加锁进行处理。在进行加锁处理时候,synchronized作为java的内置锁,同时也是java关键字,最为被人熟知,即使是最初级的j...

阿姆斯特朗回旋炮
07/18
0
0
Working with Threads-Java in a Nutshell, 6th

In order to work effectively with multithreaded code, it’s important to have the basic facts about monitors and locks at your command. This checklist contains the main facts th......

Beaver_
2015/03/21
0
0
java高并发锁的3种实现

初级技巧 - 乐观锁 乐观锁适合这样的场景:读不会冲突,写会冲突。同时读的频率远大于写。 以下面的代码为例,悲观锁的实现: Java代码 public Object get(Object key) { synchronized(map) ...

赵蕊
2017/06/09
248
1

没有更多内容

加载失败,请刷新页面

加载更多

解析如何用爬虫程序批量采集梨视频数据

本文介绍如何使用爬虫程序免费采集梨视频拍客的视频标题、链接、简介等信息。 采集字段: 视频标题 视频链接 点赞数 视频时长 发布时间 拍客 拍客链接 视频简介 标签 功能点目录: 如何对采集...

技术阿飞
10分钟前
1
0
《阿里铁军》的读书笔记和读后感范文2600字

《阿里铁军》的读书笔记和读后感范文2600字: 在中国互联网,有一个流传很广的说法是,百度强在技术,腾讯强在产品,阿里强在运营。虽然发展到今天,已经不能再用这样简单的视角来看待这三个...

原创小博客
39分钟前
4
0
怎样实际项目中运用责任链模式

1 模式概要 1.1 简介 责任链模式为请求创建一个接收者对象链,每个接收者都包含对另一个接收者的引用,如果一个对象不能处理该请求,那么它会把请求传给下一个接收者,依此类推 责任链模式避...

小刀爱编程
54分钟前
2
0
【宇润日常疯测-004】JS 遍历数组如何快!快!快!

首先,我就是一后端全栈,对前端也只是会用罢了。闲的无聊来测测,不深究,只看表面,不喜勿喷! 遍历数组在写 JS 代码时候一定是经常用的,那么怎么遍历能达到最高效率呢,很多人一定没有测...

宇润
57分钟前
11
2
Linux系统如何定制History输出格式

Linux系统使用History命令来查看系统的运行记录,从而找出一些问题。但是History输出的数据中常常没有时间等信息。本文就来教大家Linux系统如何定制History输出格式。   具体方法如下 以r...

linuxprobe16
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部