尽管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(),这样可以避免死锁或者资源浪费。
我们知道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());
}
}
下面我们使用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;inew AtomicIntegerLock(0);
t[i]=new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;jfor(int i=0;i//main线程等待前面开启的所有线程结束
for(int i=0;itry {
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;i0;
t[i]=new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;jlock){
++synValue;
}
}
}
});
}
for(int i=0;i//main线程等待前面开启的所有线程结束
for(int i=0;itry {
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;inew AtomicIntegerLock(0);
t[i]=new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;jfor(int i=0;i//main线程等待前面开启的所有线程结束
for(int i=0;itry {
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;inew AtomicInteger(0);
t[i]=new Thread(new Runnable(){
@Override
public void run() {
for(int j=0;jfor(int i=0;i//main线程等待前面开启的所有线程结束
for(int i=0;itry {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("使用原子操作类AtomicInteger所花费的时间为:"+(System.nanoTime()-begin));
}
}
运行结果如下:
使用lock所花费的时间为:493427269
使用原子操作类AtomicInteger所花费的时间为:85106267
可想可知,使用CAS指令确实更要快的多。