synchronized与ReentrantLock的介绍、使用、适合场景及比较
2017-08-17 14:14
381 查看
JDK 5.0为开发人员开发高性能的并发应用程序提供了一些很有效的新选择,目前存在两种锁机制:synchronized和Lock,Lock接口及其
实现类是JDK5增加的内容,ReentrantLock是Lock的实现。在实际的工作中,大家对synchronized和ReentrantLock都使用的比较多,今天对这
两种锁机制进行了总结并分享给各位朋友们,希望对大家有所帮助。
一、synchronized
1).介绍
synchronized 是Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有
一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必
须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object
中的非加锁代码块。
2).使用
很多朋友都知道synchronized可以对整个方法同步,也可以对方法的部分代码块进行同步。当synchronized修饰方法的时候,synchronized
是对象级的同步,意思就是说对于某个对象里面的被synchronized修饰的多个方法和synchronized(this)的代码块,当某一个线程访问一个synch
ronized修饰的方法或执行synchronized(this)的代码块,其它线程访问该对象内的synchronized修饰的方法或执行synchronized(this)的代码块
将会处于等待状态,当之前得到锁的线程执行完方法或代码块时,其它线程才可以访问。下面通过代码的方式给大家详细介绍:
1.synchronized修饰方法,表示同步作用于当前类的对象,某一个线程访问一个synchronized修饰的方法时,其它线程访问该对象内的
synchronized修饰的方法将会处于等待状态,代码如下:
[java] view
plain copy
public class Ceshi{
public synchronized int add(int a,int b)
{
&
1fff7
nbsp; return a+b;
}
public synchronized int subtract(int a,int b)
{
return a-b;
}
}
2.synchronized修饰代码块
第一种:修饰普通对象即synchronized(obj),如string实例、其它类的实例。表示当多个线程访问同一个代码块时,如果obj相同,它们将
会起到同步作用,同一时间只能允许一个线程执行,代码如下:
[java] view
plain copy
public class SynTest {
public static void main(String[] argv) {
new TestThread("12345678asdf").start();
new TestThread("12345678asdf").start();
}
public void loop(String random) {
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 传入的random:" + random);
synchronized (random) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 执行循环结束");
}
}
}
class TestThread extends Thread {
private String name;
public TestThread(String name) {
super();
this.name = name;
}
public void run() {
SynTest syntest = new SynTest();
syntest.loop(name);
}
}
在上面代码中当第一个线程访问时,虽然线程2已经进入loop方法,但是由于它们传入的random相同,所以线程2将处于等待状态,当线程1
执行完,线程2才能执行。下图是运行结果:
第二种:修饰类的字节码即synchronized(obj)中obj为某个类的class。虽然多个线程对该类的不同的实例上操作synchronized(class)修
饰的代码块,但是同一时刻只允许一个线程访问。(即使synchronized(class)在不同的方法内,都会同步)。代码如下:
[java] view
plain copy
public class SynTest {
public static void main(String[] argv) {
SynTest syn1=new SynTest();
SynTest syn2=new SynTest();
new TestThread(syn1).start();
new TestThread(syn2).start();
}
public void loop() {
System.out.println("thread name:" + Thread.currentThread().getName());
synchronized (SynTest.class) {
System.out.println(" thread name:"
+ Thread.currentThread().getName() + " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 执行循环结束");
}
}
}
class TestThread extends Thread {
private SynTest syn;
public TestThread(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop();
}
}
在上面代码中当第一个线程访问时,虽然线程2已经进入loop方法,虽然它们操作的不同的SynTest的实例,但是synchronized修饰的
同一个类的字节码,所以线程2将处于等待状态,当线程1执行完,线程2才能执行。下图是运行结果:
3.synchronized修饰方法和代码块结合
synchronized修饰方法是作用在类的实例上,如果代码块也是修饰到改实例的话,他们就会达到互斥的效果。代码如下:
[java] view
plain copy
public class SynTest {
public static void main(String[] argv) {
SynTest syn1=new SynTest();
new TestThread(syn1).start();
new TestThread1(syn1).start();
}
//循环方法
public void loop() {
System.out.println("thread name:" + Thread.currentThread().getName());
synchronized (this) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 执行循环结束");
}
}
//循环方法1
public synchronized void loop1() {
System.out.println("thread name:" + Thread.currentThread().getName());
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 执行循环结束");
}
}
//测试线程
class TestThread extends Thread {
private SynTest syn;
public TestThread(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop();
}
}
//测试线程1
class TestThread1 extends Thread {
private SynTest syn;
public TestThread1(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop1();
}
}
从上面的代码中,我们可以看出在loop()方法中,修饰的是作用于SynTest实例的代码块(synchronized (this)),在loop1中synchronized
是修饰的该方法。当线程1访问loop方法的synchronized (this)的代码块时,线程2将会处于等待状态。下面是运行结果:
3).使用场景
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化
synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
二、ReentrantLock
1).介绍
在java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就
为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synch
ronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳
的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,
然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,
就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchron
ized 块时,才释放锁。
上面这一段是负责ibm网站上的描述,其实ReentrantLock是一个可重入的互斥锁,重入锁是一种递归无阻塞的同步机制。ReentrantLock由
最近成功获取锁,还没有释放的线程所拥有,当锁被另一个线程拥有时,调用lock的线程可以成功获取锁。如果锁已经被当前线程拥有,当前线程会
立即返回。
ReentrantLock可以等同于synchronized使用,但是比synchronized有更强的功能、可以提供更灵活的锁机制、同时减少死锁的发生概率。
2).使用
1.简单使用,使用下面的结构形式:
[java] view
plain copy
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) { //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
try {
//操作
} finally {
lock.unlock();
}
}
我们按照上面的结构编写测试demo:
[java] view
plain copy
public class SynTest {
private ReentrantLock lock = new ReentrantLock();//乐观锁
public static void main(String[] argv) {
SynTest syn1 = new SynTest();
new TestThread(syn1).start();
new TestThread(syn1).start();
}
// 循环方法
public void loop() {
System.out.println("thread name:" + Thread.currentThread().getName());
lock.lock(); // 加锁
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 执行循环结束");
lock.unlock();//执行完成释放锁
}
}
// 测试线程
class TestThread extends Thread {
private SynTest syn;
public TestThread(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop();
}
}
执行结果:
2.实际工作中使用:
上面的简单使用,不能很好在多线程互斥的条件下提高代码的并发性和高效性,下面我们以demo的形式给大家展示如果高效的使用。
[java] view
plain copy
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SynTest {
// 放并发锁的map
public static Map<String, Lock> currentACCount = new ConcurrentHashMap<String, Lock>();
// 放账户金额,本来应该放在数据库的,为了测试就放入map中
public static Map<String, Long> accountMoney = new ConcurrentHashMap<String, Long>();
public static void main(String[] argv) {
new TestThread("100001",100).start();
new TestThread("100001",10).start();
new TestThread("100002",100).start();
new TestThread("100002",-10).start();
}
/**
* 获取锁
*
* @param key
* @return
*/
public static synchronized Lock getKey(String key) {
Lock obj = currentACCount.get(key);
if (obj == null) {
obj = new ReentrantLock();
currentACCount.put(key, obj);
}
System.out.println("获得key:" + key + ":" + obj);
return obj;
}
/**
* 修改账户金额
*
* @param account
* 账户
* @param money
* 金额 (单位:分)
*/
public static void updateAccountMoney(String account, Long money) {
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + ",money=" + money);
Lock lock = getKey(account);
//当锁已经被其它线程获取到,该线程将会处于等待状态
lock.lock();
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + "获取到锁成功");
long oriAmount = accountMoney.get(account)==null?0:accountMoney.get(account);
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + " 账户原金额:oriAmount=" + oriAmount);
long amount = oriAmount + money;
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + " 更新后金额:amount=" + amount);
accountMoney.put(account, amount);
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + "释放锁");
lock.unlock();
}
}
// 测试线程
class TestThread extends Thread {
private String account;
private long money;
public TestThread(String account, long money) {
super();
this.account = account;
this.money = money;
}
public void run() {
SynTest.updateAccountMoney(account, money);
}
}
从demo中我们看到,我们将实现模拟多个线程对不同的账户的账户金额进行修改,然后重新保存。其中线程1、线程2是对账户100001进行操作,
线程3、线程4对100002进行操作。它们在运行过程中只有线程1与线程2,线程3与线程4互斥,不同的账户同时操作是互不影响的,这样就能大大提高性
能,也是推荐大家使用的方式,我们看一下运行结果:
[java] view
plain copy
thread name:Thread-3 账号:100002,money=-10
thread name:Thread-2 账号:100002,money=100
thread name:Thread-0 账号:100001,money=100
thread name:Thread-1 账号:100001,money=10
获得key:100002:java.util.concurrent.locks.ReentrantLock@7f5f5897[Unlocked]
获得key:100001:java.util.concurrent.locks.ReentrantLock@4cb162d5[Unlocked]
thread name:Thread-1 账号:100001获取到锁成功
获得key:100001:java.util.concurrent.locks.ReentrantLock@4cb162d5[Locked by thread Thread-1]
thread name:Thread-3 账号:100002获取到锁成功
thread name:Thread-3 账号:100002 账户原金额:oriAmount=0
thread name:Thread-3 账号:100002 更新后金额:amount=-10
thread name:Thread-3 账号:100002释放锁
thread name:Thread-1 账号:100001 账户原金额:oriAmount=0
thread name:Thread-1 账号:100001 更新后金额:amount=10
获得key:100002:java.util.concurrent.locks.ReentrantLock@7f5f5897[Locked by thread Thread-3]
thread name:Thread-1 账号:100001释放锁
thread name:Thread-2 账号:100002获取到锁成功
thread name:Thread-2 账号:100002 账户原金额:oriAmount=-10
thread name:Thread-2 账号:100002 更新后金额:amount=90
thread name:Thread-0 账号:100001获取到锁成功
thread name:Thread-2 账号:100002释放锁
thread name:Thread-0 账号:100001 账户原金额:oriAmount=10
thread name:Thread-0 账号:100001 更新后金额:amount=110
thread name:Thread-0 账号:100001释放锁
从结果中我们可以看出Thread-1在Thread-3还未释放对100002账号操作的锁时,获取到了对100001账户操作的锁,而Thread-2在Thread-3
释放锁之后才获取到对100002账户操作的锁,Thread-0在Thread-1释放锁之后才获取到对100001账户操作的锁.所以看出效率比单个的lock效率高了
很多。
3).使用场景
由于ReentrantLock但是当同步非常激烈的时候,还能维持常态。所以比较适合高并发的场景。
三、synchronized与ReentrantLock比较
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的,因为在资源竞争不激烈的情形下,ReentrantLock性能稍
微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
下面一段话摘自于IBM网站上的一篇文章,写的非常好,在此分享一下:
什么时候选择用 ReentrantLock 代替 synchronized?
既然如此,我们什么时候才应该使用 ReentrantLock 呢?答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁
等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记
住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不
合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够
找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
结束语:
Lock 框架是同步的兼容替代品,它提供了 synchronized 没有提供的许多特性,它的实现在争用下提供了更好的性能。但是,这些明显存在的
好处,还不足以成为用 ReentrantLock 代替 synchronized 的理由。相反,应当根据您是否 需要 ReentrantLock 的能力来作出选择。大多数情况下,
您不应当选择它 —— synchronized 工作得很好,可以在所有 JVM 上工作,更多的开发人员了解它,而且不太容易出错。只有在真正需要 Lock 的时
候才用它。在这些情况下,您会很高兴拥有这款工具。
实现类是JDK5增加的内容,ReentrantLock是Lock的实现。在实际的工作中,大家对synchronized和ReentrantLock都使用的比较多,今天对这
两种锁机制进行了总结并分享给各位朋友们,希望对大家有所帮助。
一、synchronized
1).介绍
synchronized 是Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有
一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必
须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object
中的非加锁代码块。
2).使用
很多朋友都知道synchronized可以对整个方法同步,也可以对方法的部分代码块进行同步。当synchronized修饰方法的时候,synchronized
是对象级的同步,意思就是说对于某个对象里面的被synchronized修饰的多个方法和synchronized(this)的代码块,当某一个线程访问一个synch
ronized修饰的方法或执行synchronized(this)的代码块,其它线程访问该对象内的synchronized修饰的方法或执行synchronized(this)的代码块
将会处于等待状态,当之前得到锁的线程执行完方法或代码块时,其它线程才可以访问。下面通过代码的方式给大家详细介绍:
1.synchronized修饰方法,表示同步作用于当前类的对象,某一个线程访问一个synchronized修饰的方法时,其它线程访问该对象内的
synchronized修饰的方法将会处于等待状态,代码如下:
[java] view
plain copy
public class Ceshi{
public synchronized int add(int a,int b)
{
&
1fff7
nbsp; return a+b;
}
public synchronized int subtract(int a,int b)
{
return a-b;
}
}
2.synchronized修饰代码块
第一种:修饰普通对象即synchronized(obj),如string实例、其它类的实例。表示当多个线程访问同一个代码块时,如果obj相同,它们将
会起到同步作用,同一时间只能允许一个线程执行,代码如下:
[java] view
plain copy
public class SynTest {
public static void main(String[] argv) {
new TestThread("12345678asdf").start();
new TestThread("12345678asdf").start();
}
public void loop(String random) {
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 传入的random:" + random);
synchronized (random) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 执行循环结束");
}
}
}
class TestThread extends Thread {
private String name;
public TestThread(String name) {
super();
this.name = name;
}
public void run() {
SynTest syntest = new SynTest();
syntest.loop(name);
}
}
在上面代码中当第一个线程访问时,虽然线程2已经进入loop方法,但是由于它们传入的random相同,所以线程2将处于等待状态,当线程1
执行完,线程2才能执行。下图是运行结果:
第二种:修饰类的字节码即synchronized(obj)中obj为某个类的class。虽然多个线程对该类的不同的实例上操作synchronized(class)修
饰的代码块,但是同一时刻只允许一个线程访问。(即使synchronized(class)在不同的方法内,都会同步)。代码如下:
[java] view
plain copy
public class SynTest {
public static void main(String[] argv) {
SynTest syn1=new SynTest();
SynTest syn2=new SynTest();
new TestThread(syn1).start();
new TestThread(syn2).start();
}
public void loop() {
System.out.println("thread name:" + Thread.currentThread().getName());
synchronized (SynTest.class) {
System.out.println(" thread name:"
+ Thread.currentThread().getName() + " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 执行循环结束");
}
}
}
class TestThread extends Thread {
private SynTest syn;
public TestThread(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop();
}
}
在上面代码中当第一个线程访问时,虽然线程2已经进入loop方法,虽然它们操作的不同的SynTest的实例,但是synchronized修饰的
同一个类的字节码,所以线程2将处于等待状态,当线程1执行完,线程2才能执行。下图是运行结果:
3.synchronized修饰方法和代码块结合
synchronized修饰方法是作用在类的实例上,如果代码块也是修饰到改实例的话,他们就会达到互斥的效果。代码如下:
[java] view
plain copy
public class SynTest {
public static void main(String[] argv) {
SynTest syn1=new SynTest();
new TestThread(syn1).start();
new TestThread1(syn1).start();
}
//循环方法
public void loop() {
System.out.println("thread name:" + Thread.currentThread().getName());
synchronized (this) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:"
+ Thread.currentThread().getName() + " 执行循环结束");
}
}
//循环方法1
public synchronized void loop1() {
System.out.println("thread name:" + Thread.currentThread().getName());
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 执行循环结束");
}
}
//测试线程
class TestThread extends Thread {
private SynTest syn;
public TestThread(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop();
}
}
//测试线程1
class TestThread1 extends Thread {
private SynTest syn;
public TestThread1(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop1();
}
}
从上面的代码中,我们可以看出在loop()方法中,修饰的是作用于SynTest实例的代码块(synchronized (this)),在loop1中synchronized
是修饰的该方法。当线程1访问loop方法的synchronized (this)的代码块时,线程2将会处于等待状态。下面是运行结果:
3).使用场景
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化
synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
二、ReentrantLock
1).介绍
在java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就
为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synch
ronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳
的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,
然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,
就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchron
ized 块时,才释放锁。
上面这一段是负责ibm网站上的描述,其实ReentrantLock是一个可重入的互斥锁,重入锁是一种递归无阻塞的同步机制。ReentrantLock由
最近成功获取锁,还没有释放的线程所拥有,当锁被另一个线程拥有时,调用lock的线程可以成功获取锁。如果锁已经被当前线程拥有,当前线程会
立即返回。
ReentrantLock可以等同于synchronized使用,但是比synchronized有更强的功能、可以提供更灵活的锁机制、同时减少死锁的发生概率。
2).使用
1.简单使用,使用下面的结构形式:
[java] view
plain copy
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) { //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
try {
//操作
} finally {
lock.unlock();
}
}
我们按照上面的结构编写测试demo:
[java] view
plain copy
public class SynTest {
private ReentrantLock lock = new ReentrantLock();//乐观锁
public static void main(String[] argv) {
SynTest syn1 = new SynTest();
new TestThread(syn1).start();
new TestThread(syn1).start();
}
// 循环方法
public void loop() {
System.out.println("thread name:" + Thread.currentThread().getName());
lock.lock(); // 加锁
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 开始执行循环");
for (int i = 0; i < 10; i++) {
System.out.println("thread name:"
+ Thread.currentThread().getName() + " i=" + i);
}
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 执行循环结束");
lock.unlock();//执行完成释放锁
}
}
// 测试线程
class TestThread extends Thread {
private SynTest syn;
public TestThread(SynTest syn) {
super();
this.syn = syn;
}
public void run() {
syn.loop();
}
}
执行结果:
2.实际工作中使用:
上面的简单使用,不能很好在多线程互斥的条件下提高代码的并发性和高效性,下面我们以demo的形式给大家展示如果高效的使用。
[java] view
plain copy
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SynTest {
// 放并发锁的map
public static Map<String, Lock> currentACCount = new ConcurrentHashMap<String, Lock>();
// 放账户金额,本来应该放在数据库的,为了测试就放入map中
public static Map<String, Long> accountMoney = new ConcurrentHashMap<String, Long>();
public static void main(String[] argv) {
new TestThread("100001",100).start();
new TestThread("100001",10).start();
new TestThread("100002",100).start();
new TestThread("100002",-10).start();
}
/**
* 获取锁
*
* @param key
* @return
*/
public static synchronized Lock getKey(String key) {
Lock obj = currentACCount.get(key);
if (obj == null) {
obj = new ReentrantLock();
currentACCount.put(key, obj);
}
System.out.println("获得key:" + key + ":" + obj);
return obj;
}
/**
* 修改账户金额
*
* @param account
* 账户
* @param money
* 金额 (单位:分)
*/
public static void updateAccountMoney(String account, Long money) {
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + ",money=" + money);
Lock lock = getKey(account);
//当锁已经被其它线程获取到,该线程将会处于等待状态
lock.lock();
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + "获取到锁成功");
long oriAmount = accountMoney.get(account)==null?0:accountMoney.get(account);
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + " 账户原金额:oriAmount=" + oriAmount);
long amount = oriAmount + money;
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + " 更新后金额:amount=" + amount);
accountMoney.put(account, amount);
System.out.println("thread name:" + Thread.currentThread().getName()
+ " 账号:" + account + "释放锁");
lock.unlock();
}
}
// 测试线程
class TestThread extends Thread {
private String account;
private long money;
public TestThread(String account, long money) {
super();
this.account = account;
this.money = money;
}
public void run() {
SynTest.updateAccountMoney(account, money);
}
}
从demo中我们看到,我们将实现模拟多个线程对不同的账户的账户金额进行修改,然后重新保存。其中线程1、线程2是对账户100001进行操作,
线程3、线程4对100002进行操作。它们在运行过程中只有线程1与线程2,线程3与线程4互斥,不同的账户同时操作是互不影响的,这样就能大大提高性
能,也是推荐大家使用的方式,我们看一下运行结果:
[java] view
plain copy
thread name:Thread-3 账号:100002,money=-10
thread name:Thread-2 账号:100002,money=100
thread name:Thread-0 账号:100001,money=100
thread name:Thread-1 账号:100001,money=10
获得key:100002:java.util.concurrent.locks.ReentrantLock@7f5f5897[Unlocked]
获得key:100001:java.util.concurrent.locks.ReentrantLock@4cb162d5[Unlocked]
thread name:Thread-1 账号:100001获取到锁成功
获得key:100001:java.util.concurrent.locks.ReentrantLock@4cb162d5[Locked by thread Thread-1]
thread name:Thread-3 账号:100002获取到锁成功
thread name:Thread-3 账号:100002 账户原金额:oriAmount=0
thread name:Thread-3 账号:100002 更新后金额:amount=-10
thread name:Thread-3 账号:100002释放锁
thread name:Thread-1 账号:100001 账户原金额:oriAmount=0
thread name:Thread-1 账号:100001 更新后金额:amount=10
获得key:100002:java.util.concurrent.locks.ReentrantLock@7f5f5897[Locked by thread Thread-3]
thread name:Thread-1 账号:100001释放锁
thread name:Thread-2 账号:100002获取到锁成功
thread name:Thread-2 账号:100002 账户原金额:oriAmount=-10
thread name:Thread-2 账号:100002 更新后金额:amount=90
thread name:Thread-0 账号:100001获取到锁成功
thread name:Thread-2 账号:100002释放锁
thread name:Thread-0 账号:100001 账户原金额:oriAmount=10
thread name:Thread-0 账号:100001 更新后金额:amount=110
thread name:Thread-0 账号:100001释放锁
从结果中我们可以看出Thread-1在Thread-3还未释放对100002账号操作的锁时,获取到了对100001账户操作的锁,而Thread-2在Thread-3
释放锁之后才获取到对100002账户操作的锁,Thread-0在Thread-1释放锁之后才获取到对100001账户操作的锁.所以看出效率比单个的lock效率高了
很多。
3).使用场景
由于ReentrantLock但是当同步非常激烈的时候,还能维持常态。所以比较适合高并发的场景。
三、synchronized与ReentrantLock比较
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的,因为在资源竞争不激烈的情形下,ReentrantLock性能稍
微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
下面一段话摘自于IBM网站上的一篇文章,写的非常好,在此分享一下:
什么时候选择用 ReentrantLock 代替 synchronized?
既然如此,我们什么时候才应该使用 ReentrantLock 呢?答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候,比如时间锁
等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记
住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不
合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够
找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
结束语:
Lock 框架是同步的兼容替代品,它提供了 synchronized 没有提供的许多特性,它的实现在争用下提供了更好的性能。但是,这些明显存在的
好处,还不足以成为用 ReentrantLock 代替 synchronized 的理由。相反,应当根据您是否 需要 ReentrantLock 的能力来作出选择。大多数情况下,
您不应当选择它 —— synchronized 工作得很好,可以在所有 JVM 上工作,更多的开发人员了解它,而且不太容易出错。只有在真正需要 Lock 的时
候才用它。在这些情况下,您会很高兴拥有这款工具。
相关文章推荐
- synchronized与ReentrantLock的介绍、使用、适合场景及比较
- synchronized与ReentrantLock的介绍、使用、适合场景及比较
- java多线程基础---synchronized与ReentrantReadWriteLock的介绍和比较
- 比较 ReentrantLock 和 synchronized 的使用和可伸缩性
- java多线程基础---synchronized与ReentrantReadWriteLock的介绍与比较
- java ReentrantLock可重入锁的使用场景
- synchronized和ReentrantLock到底使用哪个?
- 比较ReentrantLock和synchronized和信号量Semaphore实现的同步性能
- ReentrantLock和ReentrantReadWriteLock使用介绍
- Java 并发编程中使用 ReentrantLock 替代 synchronized 关键字原语
- Java中Synchronized与ReentrantLock的不同以及ReentrantLock的使用
- ReentrantLock的实现语义与使用场景
- Synchronized与ReentrantReadWriteLock性能比较
- ReentrantLock和synchronized比较
- Redis简介、与memcached比较、存储方式、应用场景、生产经验教训、安全设置、key的建议、安装和常用数据类型介绍、ServiceStack.Redis使用(1)
- 各种同步方法性能比较(synchronized,ReentrantLock,Atomic)
- 各种同步方法性能比较(synchronized,ReentrantLock,Atomic)
- ReentrantLock可重入锁的使用场景
- ReentrantLock可重入锁的原理及使用场景
- 多线程经常使用的3个关键字:synchronized、ReentrantLock、volatile