您的位置:首页 > 编程语言 > Java开发

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: