java锁机制

java的锁,相信大家都不会陌生,在前面讲集合类家族的时候提到了一个线性安全与不安全的概念,而锁这个机制,原本就是一个线性安全的保证。

 

在众多的锁机制中,大家最熟悉的莫过于synchronize关键字,这个关键字修饰的方法、类、代码块在被某处调用时候会加上锁,除非锁解开,否则其他地方完全不能调用,这种机制我们称为悲观锁:无论不加锁存不存在线性安全的问题,都给加上锁。这样的机制无疑会产生两个问题,第一是当有两处这样的锁A和B,然后两个线程X和Y,X占用了A,Y占用了B,X在等待Y释放B,Y在等X释放A,这样就会产生死锁的问题;第二是一旦这种机制被多个线程频繁使用,效率将会受到非常大的影响。例子:

 设计一个类

 

public class CASTest {
	
	private Integer number = 0;
	
	public void addNumber() {
		number ++;
	}
	
	public Integer getNumber() {
		return number;
	}
}	

 

测试:

 

                CASTest test = new CASTest();
		Runnable runnable = new Runnable() {

			@Override
			public void run() {
				for (int i=0; i<10000; i++) {
					test.addNumber();
				}
				
			}
			
		};
		long from = new Date().getTime();  

		new Thread(runnable).start();
		
		runnable.run();
		System.out.println(test.getNumber());
		System.out.println(new Date().getTime() - from);

 

结果:

12359
3

可以看到,结果中number并不是想象中的20000,现在加上synchronize:

 

public class CASTest {
	
	private Integer number = 0;
	
	public synchronized void addNumber() {
		number ++;
	}
	
	public Integer getNumber() {
		return number;
	}
}

结果:

 

20000
6

可以看到,虽然结果完美的变成了20000,但是效率降低了一倍,两个线程都有如此大的效率问题,可想而知悲观锁的性能问题有多大。

 

在JDK1.5以后有一种新的机制叫做CAS,这种机制会保存代码的原值,在代码做修改的时候,它会比较代码的当前值和原值,当且仅仅当二者相等的时候才修改其值。这种锁被称为乐观锁。例子:

public class CASTest {	
	private AtomicInteger number = new AtomicInteger(0);
	public void addNumber() {
		number.getAndAdd(1);
	}
	
	public Integer getNumber() {
		return number.get();
	}
}

 

测试的调用同上

20000
2

结果可以看出,在误差范围内时间几乎和普通变量无二,而且最终值也是20000.,这充分说明了乐观锁不仅基本可以达到悲观锁的效果,而且在效率上大大提高。

 

来看下用到了乐观锁的AtomicInteger的源代码:

 

/**
 * An {@code int} value that may be updated atomically.  See the
 * {@link java.util.concurrent.atomic} package specification for
 * description of the properties of atomic variables. An
 * {@code AtomicInteger} is used in applications such as atomically
 * incremented counters, and cannot be used as a replacement for an
 * {@link java.lang.Integer}. However, this class does extend
 * {@code Number} to allow uniform access by tools and utilities that
 * deal with numerically-based classes.
 *
 * @since 1.5
 * @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

 

    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

对于所有的原子类而言,最终都是调用了UnSafe类。

 

Unsafe类中有大量的native方法,可以下载OpenJDK的源码,在openjdk/openjdk/hotspot/src/share/vm/prims/unsafe.cpp中有源代码

随便看一个compareAndSwapInt方法:

 

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

native方法的源码似乎不太容易读懂,但是我们可以看到这里是将用Atomic::cmpxchg方法获取到的值来和e做比较。

 

 

jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
  assert(sizeof(jbyte) == 1, "assumption.");
  uintptr_t dest_addr = (uintptr_t)dest;
  uintptr_t offset = dest_addr % sizeof(jint);
  volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
  jint cur = *dest_int;
  jbyte* cur_as_bytes = (jbyte*)(&cur);
  jint new_val = cur;
  jbyte* new_val_as_bytes = (jbyte*)(&new_val);
  new_val_as_bytes[offset] = exchange_value;
  while (cur_as_bytes[offset] == compare_value) {
    jint res = cmpxchg(new_val, dest_int, cur);
    if (res == cur) break;
    cur = res;
    new_val = cur;
    new_val_as_bytes[offset] = exchange_value;
  }
  return cur_as_bytes[offset];
}


从这个方法的源码中,我们大致看出其利用了存储值的地方取出了现有值并返回。

 

 

源码姑且一看(我也没有完全看懂),有兴趣的自己接着研究。

 

这里还有个问题,就是我们看到在AtomicInteger类中用volatile修饰了value变量,这个volatile修饰符有什么用处呢。

 

据说这个修饰符是为了不同线程的可见性,可以认为有了这个修饰符修饰,线程间可见性比没有这个更强:

 

 

public class VolatileTest extends Thread{
	
	private Integer number = 0;
	
	private Boolean flag = true;
	
	public void run() {
		while (flag) {
			try {
				Thread.sleep(1);
				System.out.println("xxxxxxxxxxx");
				number ++;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public Integer getNumber() {
		return number;
	}
	
	public void stops() {
		flag = false;
	}
	

	public static void main(String[] args) {
		
		VolatileTest test = new VolatileTest();
		new Thread(test).start();
		try {
			Thread.sleep(1000);
			test.stops();
			System.out.println(test.getNumber());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

。。。。
xxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxxx
974
xxxxxxxxxxx
可以看见,在stop之后线程并没有马上停下。

 

加上private volatile Boolean flag = true;

结果:

xxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxxx
985

这个结果并不是每次都生效,只是大多数时候是这样,因为本来就是一个机制问题。

 


除了上述锁机制以外,还有一种reentrantlock机制,reentrantlock原本也属于CAS内容的一部分,在使用的时候它更好地替代了synchronize,例子:
 

public class LockTest {
	
	Lock lock = new ReentrantLock();
	
	public void doSomething() {
		lock.lock();
		System.out.println("xxxxxxx");
		try {
			Thread.sleep(1000);
			System.out.println("yyyyy");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		
	}

	public static void main(String[] args) {
		LockTest test = new LockTest();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.doSomething();
				
			}
		}).start();
		test.doSomething();
	}

}

结果:

xxxxxxx
yyyyy
xxxxxxx
yyyyy

从这里可以看到,由于lock的存在,dosomething方法就被锁定不能被其他线程调用了,这看起来有点像synchronize,而且由于采用了类对象的方式实现,在使用的时候比synchronize更可控和灵活。

此外JDK官方还提出了ReentrantLock是一种不会死锁的锁机制,原因是当线程await的时候,ReentrantLock将其视为释放。

而且ReentrantLock可以用传入的参数将其定位公平锁,虽然公平锁的性能较低,但是在有些业务逻辑下锁的公平性还是有必要的。

 

ReentrantLock机制采用的是阻塞队列实现锁机制的,他有一个内参state,在调用lock方法的时候会先看这个state是否为0,如果不是则阻塞在队列中循环等待state变化,state是一个volatile修饰的变量,因此只要锁一被释放就会被其他线程见到并使用。此外查看和改变这个state的值就是采用了unsafe类,即采用了CAS机制。

 

具体的源码我不做详解了,可以看这篇博客:https://www.cnblogs.com/xrq730/p/4979021.html

 

总结下java的锁机制,首先比较 普通写法、变量加volatile修饰、用CAS、synchronize修饰 这四种,从左至右效率依次降低,线性安全依次增加。

 

综合来讲个人比较推荐volatile和CAS一起使用来实现线性安全,高效且极少出错,在用到锁的时候可以尝试使用ReentrantLock。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值