文档章节

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

© 著作权归作者所有

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

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

编程大侠
04/09
0
0
synchronized ReentrantLock 比较分析

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

阿姆斯特朗回旋炮
07/18
0
0
Terracotta tc-config.xml配置说明(这个真的是转的)

<?xml version="1.0" encoding="UTF-8" ?> <!-- All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved. --> <!-- tc-config-reference.xml This is a ......

亮liang
2015/08/25
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
synchronized与ThreadLocal

synchronized是实现java的同步机制。同步机制是为了实现同步多线程对相同资源的并发访问控制。保证多线程之间的通信。 同步的主要目的是保证多线程间的数据共享。同步会带来巨大的性能开销,...

bigYuan
2013/07/18
0
2

没有更多内容

加载失败,请刷新页面

加载更多

Java Web--增删改查之二界面后台java代码(转载参考)

/** *  *//** * @author Administrator * */package dao; import java.sql.*;public class DBConn {/** * 链接数据库 * @return */  ...

小橙子的曼曼
19分钟前
1
0
Redis源码阅读笔记-对象及其类型和编码

总结之《Redis设计与实现》 对象 Redis中是使用对象来便是数据库中的键和值。 结构 // server.h...#define LRU_BITS 24...typedef struct redisObject { unsigned type:4; ...

Jian_Ming
32分钟前
1
0
laravel框架常用目录路径

laravel框架常用目录路径 app_path()app_path函数返回app目录的绝对路径:$path = app_path();你还可以使用app_path函数为相对于app目录的给定文件生成绝对路径:$path = app_p...

高处胜寒
33分钟前
0
0
记一次winserver2003系统,https无法访问,内存占用持续增加,解决办法

先交代一下环境: win server2003系统,系统装在hyper-v虚拟机里 大概2016年底的镜像,距离今天两年左右 病症:大概9月10号左右用这个镜像还可以访问https,但是今天用这个镜像新装的系统,就...

阳阳露
48分钟前
3
0
Vue学习资料

一直以为Vue是依赖nodejs的。 作为前端也可以耦合性就很低了。 //npm包管理器 进行管理npm install vue//初始化一个项目vue init//本地调试npm run dev//编译完成 ...

大灰狼wow
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部