java多线程基础---synchronized与ReentrantReadWriteLock的介绍和比较
2015-01-15 11:55
996 查看
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候 线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定, 如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断 如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情 ReentrantLock获取锁定与三种方式: a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁 b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false; c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false; d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断 2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中 3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
【synchronized同步】 JDK1.5之前,实现同步主要是使用synchronized。
synchronized相信很多熟悉J2SE的人都不会对这个关键字陌生,它用于实现多个线程之间的同步,一般有两种使用方式:
1、在方法上加synchronized关键字
复制内容到剪贴板
代码:
public synchronized void f() { //do something }
2、synchronized同步代码块
复制内容到剪贴板
代码:
synchronized (mutex) { // do something }
对于这两种方式又应该着重区分是否为“static”的,因为static的成员是属于类的,非staitc的是属于具体实例的,所以在使用synchronized时应该注意方法或选择的同步变量是否为static的,如下代码:
复制内容到剪贴板
代码:
public class SyncTest { private Object mutex = new Object(); public synchronized void f1() { synchronized (mutex) { System.err.println("nonstatic method f1...."); try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { SycnThread thread1 = new SycnThread(new SyncTest()); SycnThread thread2 = new SycnThread(new SyncTest()); SycnThread thread3 = new SycnThread(new SyncTest()); thread1.start(); thread2.start(); thread3.start(); } } class SycnThread extends Thread { private SyncTest st; public SycnThread(SyncTest st) { this.st = st; } @Override public void run() { while (true) { st.f1(); } } }
在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.util.concurrent.locks下的锁实现同步】
自JDK1.5以为,Java提供了java.util.concurrent这个并发包,在它的子包locks中,提供了一系列关于锁的抽象的类。主要有两种锁ReentrantLockReentrantReadWriteLock,而其他的两个类,都是“辅助”类,如AbstractQueuedSynchronizer就是一个用于实现特殊规则锁的抽象类,ReentrantLock和ReentrantReadWriteLock内部都有一个继承了该抽象类的内部类,用于实现特定锁的功能。下文主要介绍:ReentrantLock和ReentrantReadWriteLock,ReentrantReadWriteLock与ReentrantLock基本一样,只是ReentrantReadWriteLock实现了特殊规则(读写锁),在ReentrantReadWriteLock中有两个内部类ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock(实际上不止两个内部类,还有实现AbstractQueuedSynchronizer的Sync等等),这两个类分别可以使用ReentrantReadWriteLock的readLock()和writeLock()返回,该读写锁的规则是:只要没有writer,读取锁定可以由多个reader
线程同时保持,而写入锁定是独占的。
先介绍
可重入的锁ReentrantLock:使用ReentrantLock锁最简单的一个例子:
复制内容到剪贴板
代码:
Lock lock = new ReentrantLock(); try { lock.lcok(); // do something } finally { lock.unlock(); }
上面这段代码,首先创建了一个lock,然后调用它的lock()方法,开启锁定,在最后调用它的unlock()解除锁定。值得注意的时,一般在使用锁时,都应该按上面的风格书写代码,即lock.unlock()最好放在finally块,这样可以防止,执行do something时发生异常后,导致锁永远无法被释放。注意:lock与unlock一定要配对。
到此,还没发现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。
可重入的读写锁ReentrantReadWriteLock
该锁的机制特性总结如下:
复制内容到剪贴板
代码:
(a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。 (b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持 有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵. (c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高 读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。 (d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。 (e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出 UnsupportedOperationException异常。
下面还是写个小例子说明部分内容:
复制内容到剪贴板
代码:
public class ReentrantReadWriteLockSample { public static void main(String[] args) { testReadLock(); // testWriteLock(); } public static void testReadLock() { final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport(); support.initCache(); Runnable runnable = new Runnable() { public void run() { support.get("test"); } }; new Thread(runnable).start(); new Thread(runnable).start(); new Thread(new Runnable() { public void run() { support.put("test", "test"); } }).start(); } public static void testWriteLock() { final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport(); support.initCache(); new Thread(new Runnable() { public void run() { support.put("key1", "value1"); } }).start(); new Thread(new Runnable() { public void run() { support.put("key2", "value2"); } }).start(); new Thread(new Runnable() { public void run() { support.get("key1"); } }).start(); } } class ReadWriteLockSampleSupport { private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); private volatile boolean completed; private Map cache; public void initCache() { readLock.lock(); if(!completed) { // Must release read lock before acquiring write lock readLock.unlock(); // (1) writeLock.lock(); // (2) if(!completed) { cache = new HashMap(32); completed = true; } // Downgrade by acquiring read lock before releasing write lock readLock.lock(); // (3) writeLock.unlock(); // (4) Unlock write, still hold read } System.out.println("empty? " + cache.isEmpty()); readLock.unlock(); } public String get(String key) { readLock.lock(); System.out.println(Thread.currentThread().getName() + " read."); startTheCountdown(); try{ return cache.get(key); } finally{ readLock.unlock(); } } public String put(String key, String value) { writeLock.lock(); System.out.println(Thread.currentThread().getName() + " write."); startTheCountdown(); try{ return cache.put(key, value); } finally { writeLock.unlock(); } } /** * A simple countdown,it will stop after about 5s. */ public void startTheCountdown() { long currentTime = System.currentTimeMillis(); for(;;) { long diff = System.currentTimeMillis() - currentTime; if(diff > 5000) { break; } } } }
复制内容到剪贴板
代码:
这个例子改造自JDK的API提供的示例,其中ReadWriteLockSampleSupport辅助类负责维护一个Map,当然前提是这个Map大部分的多线程 下都是读取,只有很少的比例是多线程竞争修改Map的值。其中的initCache()简单的说明了特性(a),(b).在这个方法中如果把注释(1)和(2) 处的代码调换位置,就会发现轻而易举的死锁了,当然是因为特性(1)的作用了。而注释(3),(4)处的代码位置则再次证明了特性 (a),并 且有力的反映了特性(b)--WriteLock在cache初始化完毕之后,降级为ReadLock。另外get(),put()方法在线程获取锁之后会在方法中呆上近 5s的时间。 ReentrantReadWriteLockSample中的两个静态测试方法则分别测试了ReadLock和WriteLock的排斥性。testReadLock()中,开启三个线程 ,前两者试图获取ReadLock而后者去获取WriteLock。执行结果可以看到:ReadWriteLockSampleSupport的get()方法中的打印结果在前两个 线程中几乎同时显示,而put()中的打印结果则要等上近5s。这就说明了,ReadLock可以多线程持有并且排斥WriteLock的持有线程。 testWriteLock()中,也开启三个线程。前两个是去获取WriteLock,最后一个获取ReadLock。执行的结果是三个打印结果都有近5s的间隔时 间,这说明了WriteLock是独占的,比较独!
摘自:http://www.shangxueba.com/jingyan/86389.html
/article/8265172.html
相关文章推荐
- java多线程基础---synchronized与ReentrantReadWriteLock的介绍与比较
- Java多线程synchronized、ReentrantLock、ReentrantReadWriteLock 和StampedLock 的对比
- Java多线程——锁(Synchronized、Lock、ReentrantLock、ReadWriteLock、ReentrantReadWriteLock)
- Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- java锁:synchronized、ReadWriteLock、ReentrantReadWriteLock*
- Java多线程(十)之ReentrantReadWriteLock深入分析
- 多线程基础总结--ReentrantReadWriteLock(网摘)
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- java多线程:并发包中ReentrantReadWriteLock读写锁的原理
- java多线程学习笔记——读写锁(ReentrantReadWriteLock)
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- JAVA多线程之——读写锁 ReentrantReadWriteLock
- Java多线程系列--“JUC锁”08之 共享锁和ReentrantReadWriteLock
- Java基础:多线程之ReadWriteLock、Condition、Semaphore
- Java 8:StampedLock,ReadWriteLock以及synchronized的比较