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

java中内部锁synchronized和重入锁reentrantlock之间进行选择

2018-01-05 17:47 453 查看
重入锁reentrantlock 可以选择 公平锁和非公平锁。

内部锁synchronized 本身是 非公平锁。

从性能的角度上看, 非公平锁性能要远大于公平锁。

至于重入锁中非公平锁的性能和 内部锁synchronized的性能比较并没有谁优谁劣的情况。

以下例子是对 三者的性能对比:

下面具体的构造一个测试程序来具体考察 ReentrantLock 的性能。构造一个 计数器 Counter,启动 N 个线程对计数器进行递增操作。显然,这个递增操作需 要同步以防止数据冲突和线程干扰,为保证原子性,采用 3 种锁来实现同步,然 后查看结果。

测试环境是双核酷睿处理器,内存 3G,JDK6。

第一种是内在锁,第二种是不公平的 ReentrantLock 锁,第三种是公平的 ReentrantLock 锁。

首先定义一个计数器接口。

package locks;
public interface Counter {
public long getValue();
public void increment();
}


下面是使用内在锁的计数器类:

package lockbenchmark;
public class SynchronizedCounter implements Counter {
private long count = 0;
public long getValue() {
return count;
}
public synchronized void increment() {
count++;
}
}


下面是使用不公平 ReentrantLock 锁的计数器。

package lockbenchmark;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantUnfairCounterLockCounter implements Counter {
private volatile long count = 0;
private Lock lock;
public ReentrantUnfairCounterLockCounter() {
// 使用非公平锁,true就是公平锁
lock = new ReentrantLock(false);
}
public long getValue() {
return count;
}
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}


下面是使用公平的 ReentrantLock 锁的计数器。

package lockbenchmark;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantFairLockCounter implements Counter {
private volatile long count = 0;
private Lock lock;
public ReentrantFairLockCounter() {
// true就是公平锁
lock = new ReentrantLock(true);
}
public long getValue() {
return count;
}
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}


下面是总测试程序。

package lockbenchmark;

import java.util.concurrent.CyclicBarrier;

public class BenchmarkTest {
private Counter counter;
// 为了统一启动线程,这样好计算多线程并发运行的时间
private CyclicBarrier barrier;
private int threadNum;// 线程数
private int loopNum;// 每个线程的循环次数
private String testName;

public BenchmarkTest(Counter counter, int threadNum, int loopNum, String testName) {
this.counter = counter;
barrier = new CyclicBarrier(threadNum + 1); // 关卡计数=线程数
this.threadNum = threadNum;
this.loopNum = loopNum;
this.testName = testName;
}

public static void main(String args[]) throws Exception {
int threadNum = 2000;
int loopNum = 1000;
new BenchmarkTest(new SynchronizedCounter(), threadNum, loopNum, "内部锁").test();
new BenchmarkTest(new ReentrantUnfairCounterLockCounter(), threadNum, loopNum, "不公平重入锁").test();
new BenchmarkTest(new ReentrantFairLockCounter(), threadNum, loopNum, "公平重入锁").test();
}

public void test() throws Exception {

4000
try {
for (int i = 0; i < threadNum; i++) {
new TestThread(counter, loopNum).start();
}
long start = System.currentTimeMillis();
barrier.await(); // 等待所有任务线程创建,然后通过关卡,统一执行
barrier.await(); // 等待所有任务计算完成
long end = System.currentTimeMillis();
System.out.println(this.testName + " count value:" + counter.getValue());
System.out.println(this.testName + " 花费时间:" + (end - start) + "毫秒");
} catch (Exception e) {
throw new RuntimeException(e);
}
}

class TestThread extends Thread {
int loopNum = 100;
private Counter counter;

public TestThread(final Counter counter, int loopNum) {
this.counter = counter;
this.loopNum = loopNum;
}

public void run() {
try {
barrier.await();// 等待所有的线程开始
for (int i = 0; i < this.loopNum; i++)
counter.increment();
barrier.await();// 等待所有的线程完成
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}


对三种锁分别设置两个不同的参数:不同线程数和每个线程数的循环次数。 最后记录每种锁的运行时间(单位:ms),形成下表。

循环次数 1000

线程数20050010002000
内在锁62313406875
非公平锁3294250859
公平锁4641176104467157391
循环次数 200

线程数20050010002000
内在锁4794109265
非公平锁1632125906
公平锁7813031867113625
分析统计结果,在线程数小于 2000 的情况下,非公平可重入锁的性能要优 于内部锁。公平可重入锁的性能最差。同时发现内部锁其实也是一个非公平锁。

重入锁(ReentrantLock)与内部锁在加锁和内存语义上是相同的。从性能上看,重入锁的性能看起来胜过内部锁。在 Java 5.0 中,两者性能之间的差距比较 大;而在 Java 6 中,这种差距变得比较小。与重入锁相比,内部锁仍然具有很大 的优势,比如内部锁更为人们所熟悉,也更简洁,而且很多现有的程序已经在使 用内部锁了。重入锁是很危险的同步工具,程序员在使用重入锁时,容易产生错 误。因此,只有在内部锁不能满足需求,才需要使用重入锁。

在 Java 5.0 中,内部锁还具有另外一个优点:线程转储能够显示哪些调用框 架获得了哪些锁,并能够识别发生了死锁的线程。但 Java 虚拟机并不知道哪个 线程持有重入锁,因此在调试使用了重入锁的线程时,无法从中获得帮助信息。 这个问题在 Java 6 中得到了解决,它提供了一个管理和调试接口,锁可以使用这 个接口进行注册,并通过其他管理和调试接口,从线程转储中得到重入锁的加锁 信息。

由于内部锁是内置于 Java 虚拟机中的,它能够进行优化,因此未来的性能 改进可能更倾向于内部锁,而不是重入锁。综上所述,除非你的应用程序需要发布在 Java 5.0 上,或者需要使用重入锁的可伸缩性,否则就应该选择内部锁。

总之,ReentrantLock 锁与 Java 内在锁相比有下面的特点:

ReentrantLock 必须在 finally 块中释放锁,而使用 synchronized 同步,JVM

将确保锁会获得自动释放。

与目前的 synchronized 实现相比,争用下的 ReentrantLock 实现更具可

伸缩性。

对于 ReentrantLock ,可以有不止一个条件变量与它关联。

允许选择想要一个公平锁,还是一个不公平锁。

除非您对 Lock 的某个高级特性有明确的需要,或者有明确的证据表明

在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用 synchronized。

Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。而 且,几乎每个开发人员都熟悉 synchronized,它可以在 JVM 的所有版本中工作。

(转自 https://wuwawuwa.cn)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: