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

《Java中Lock和synchronized的比较和应用》

2016-07-30 20:07 411 查看

《Java中Lock和synchronized的比较和应用》

尽管synchronized在语法上已经足够简单了,在JDK 5之前只能借助此实现,但是由于是独占锁,性能却不高,因此JDK 5以后就开始借助于JNI来完成更高级的锁实现。

JDK 5中的锁是接口java.util.concurrent.locks.Lock。另外java.util.concurrent.locks.ReadWriteLock提供了一对可供读写并发的锁。我们从java.util.concurrent.locks.Lock的使用,可以查询API文档,这里不再说明。

java.util.concurrent.locks.Lock类,既然是锁,肯定有用于线程获取锁和释放锁的方法存在,这两个方法为:

1、void lock();

函数功能:获取锁。如果锁不可用,由于线程调度目的,将禁用此线程,并且在获得锁之前,该线程将一直处于休眠状态。

2、unlock();

函数功能:释放锁。对应于所有的获取锁,例如lock()、tryLock()、tryLock(xx)、lockInterruptibly()等操作,如果成功的话应该对应着一个unlock(),这样可以避免死锁或者资源浪费。

使用Lock同步来模拟AtomicInteger类

我们知道AtomicInteger类是一个int型原子操作类。下面我们就使用Lock类来模拟实现一个AtomicInteger。

在模拟实现之前,我们如果不太了解AtomicInteger,可以先看下这个类的API文档以及AtomicInteger源码实现。

AtomicInteger的所有原子操作都依赖于sun.misc.Unsafe类,Unsafe类中相关操作都是对应于一条与平台有关的处理器CAS指令。

使用锁Lock模拟的AtomicInteger类代码如下:

类代码虽长,但思路相当简单。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class AtomicIntegerLock {

private volatile int value;

private Lock lock = new ReentrantLock();

public AtomicIntegerLock(int value) {
this.value = value;
}

public void set(int newValue){
lock.lock();
try{
this.value = newValue;
}finally{
lock.unlock();
}
}

public final int get(){
lock.lock();
try{
return value;
}finally{
lock.unlock();
}
}

public final int getAndSet(int newValue){
lock.lock();
try{
int oldValue = value;
value = newValue;
return oldValue;
}finally{
lock.unlock();
}
}

public final int getAndAdd(int delta){
lock.lock();
try{
int oldValue = value;
value+=delta;
return oldValue;
}finally{
lock.unlock();
}
}

public final int addAndGet(int delta){
lock.lock();
try{
value+=delta;
return value;
}finally{
lock.unlock();
}
}

public final boolean getAndCompare(int expect,int newValue){
lock.lock();
try{
if(this.value == expect){
value = newValue;
return true;
}
else{
return false;
}
}finally{
lock.unlock();
}
}

public final int getAndIncrement(){
lock.lock();
try{
return value++;
}finally{
lock.unlock();
}
}

public final int getAndDecrement(){
lock.lock();
try{
return value--;
}finally{
lock.unlock();
}
}

public final int incrementAndGet(){
lock.lock();
try{
return ++value;
}finally{
lock.unlock();
}
}

public final int decrementAndGet(){
lock.lock();
try{
return --value;
}finally{
lock.unlock();
}
}

public final String  toString(){
return Integer.toString(get());
}

}


Lock同步和synchronized同步两种锁的性能

下面我们使用synchronized和Lock分别进行同步的性能比较:分别开启10个线程,每个线程计数到1000000,统计两种锁同步所花费的时间

public class TestAtomicIntegerLock {

private static int synValue = 0;
public static void main(String[] args) {
int threadNum = 10;
int maxValue = 1000000;

Thread[] t = new Thread[threadNum];
Long begin = System.nanoTime();
for(int i=0;i<threadNum;i++){
AtomicIntegerLock aIL = new AtomicIntegerLock(0);
t[i]=new Thread(new Runnable(){

@Override
public void run() {
for(int j=0;j<maxValue;j++){
aIL.getAndIncrement();
}
}

});
}
for(int i=0;i<threadNum;i++){
t[i].start();
}
//main线程等待前面开启的所有线程结束
for(int i=0;i<threadNum;i++){
try {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("使用lock所花费的时间为:"+(System.nanoTime()-begin));

int[] lock = new int[0];
begin = System.nanoTime();
for(int i=0;i<threadNum;i++){
synValue = 0;
t[i]=new Thread(new Runnable(){

@Override
public void run() {
for(int j=0;j<maxValue;j++){
synchronized(lock){
++synValue;
}
}
}

});
}
for(int i=0;i<threadNum;i++){
t[i].start();
}
//main线程等待前面开启的所有线程结束
for(int i=0;i<threadNum;i++){
try {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("使用synchronized所花费的时间为:"+(System.nanoTime()-begin));

}

}


结果如下:

使用lock所花费的时间为:489742547
使用synchronized所花费的时间为:1660636784


从时间数字来看,可以说明,使用lock的性能要好。

在《深入理解Java虚拟机》这本书上,作者说了这句话:与其说ReentrantLock性能好,还不如说synchronized还有很大优化的余地。在JDK1.6之后,人们发现synchronized与ReentrantLock的性能基本上是完全持平的(上面测试的JDK是1.8,不知道为什么没有持平)。虚拟机在未来的性能改进中肯定会更加偏向于原生的synchronized,所以还是提倡synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

无论别人怎么说,测试才是王道。当确实synchroinzed同步时我们的性能瓶颈时,我们可以用ReentrantLock来进行性能的测试,如果确实更优,我们就可以选择用ReetrantLock来进行同步。

用Lock来进行同步计数和使用AtomicInteger类计数的性能比较

纯属好奇,刚好用Lock来模拟了下AtmoicInteger,因此,我也就比较了下这两个类在开启10个线程,每个线程计数到1000000的时间。

代码如下:

import java.util.concurrent.atomic.AtomicInteger;

public class TestAtomicIntegerLock2 {

private static int synValue = 0;
public static void main(String[] args) {
int threadNum = 10;
int maxValue = 1000000;

Thread[] t = new Thread[threadNum];
Long begin = System.nanoTime();
for(int i=0;i<threadNum;i++){
AtomicIntegerLock aIL = new AtomicIntegerLock(0);
t[i]=new Thread(new Runnable(){

@Override
public void run() {
for(int j=0;j<maxValue;j++){
aIL.getAndIncrement();
}
}

});
}
for(int i=0;i<threadNum;i++){
t[i].start();
}
//main线程等待前面开启的所有线程结束
for(int i=0;i<threadNum;i++){
try {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("使用lock所花费的时间为:"+(System.nanoTime()-begin));

int[] lock = new int[0];
begin = System.nanoTime();
for(int i=0;i<threadNum;i++){
AtomicInteger ai = new AtomicInteger(0);
t[i]=new Thread(new Runnable(){

@Override
public void run() {
for(int j=0;j<maxValue;j++){
ai.incrementAndGet();
}
}

});
}
for(int i=0;i<threadNum;i++){
t[i].start();
}
//main线程等待前面开启的所有线程结束
for(int i=0;i<threadNum;i++){
try {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("使用原子操作类AtomicInteger所花费的时间为:"+(System.nanoTime()-begin));

}

}


运行结果如下:

使用lock所花费的时间为:493427269
使用原子操作类AtomicInteger所花费的时间为:85106267


可想可知,使用CAS指令确实更要快的多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: