Java锁

锁类型

锁按照不同的标准可以定义出不同类型。

  • 可重入锁/不可重入锁
  • 公平锁/非公平锁
  • 独占锁/共享锁
  • 乐观锁/悲观锁
  • 偏向锁/轻量级锁/重量级锁

可重入锁/不可重入锁

按照锁是否可以嵌套获取,我们可以将锁分为可重入锁和不可重入锁。Java中的synchronized和ReentrantLock都是可重入锁,现在基本上很少见不可重入锁,不可重入锁在嵌套获取锁的时候会阻塞出现死锁。如下代码就是一个不可重入锁,永远不会打印lock second。

public static void main(String[] args) {
	NonReentrantLock lock = new NonReentrantLock();
	lock.lock();
	System.out.println("lock first");
	lock.lock();
	System.out.println("lock second");
}

公平锁/非公平锁

按照锁的公平性可以分为公平锁和非公平锁。

公平锁:线程获取锁的顺序和线程得到锁的顺序是严格一致的,也就是A和B线程,如果A先来,那么A就必然先获取到锁。

非公平锁:和公平锁不同,如果A和B线程都来获取同一锁并阻塞等待,A和B都可能先获取到锁。

Java中ReentrantLock的空构造函数和synchronized都是非公平锁,因为要保证公平性必然会浪费一定的资源。(试想一下,对于公平锁,在锁释放时,如果线程A还没有获取到CPU资源,锁就得等待线程A获取到CPU资源然后将锁给A使用;而如果是非公平锁他发现A线程没有获取到CPU资源,他可以把锁交给已经获取到CPU资源的B线程,对锁的利用率更高)。

如果需要使用非公平锁,可以使用ReentrantLock的一个boolean型构造函数,传入true,进行创建。

public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}

独占锁/共享锁

按照锁是否可以被多个线程获取,可以分为独占锁和共享锁。Java中,ReentrantReadWriteLock读写锁中有两个锁,读锁和写锁,其中读锁就是共享锁,可以被多个线程获取;而写锁就是独占锁,在写锁被占用的时候,读锁也不能获取。

public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

写锁和其他锁(读锁和写锁)属于互斥关系,读锁和读锁可以共享,但是读锁和写锁也属于互斥关系,下面写了个例子,有兴趣的人可以跑一下。

SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " try get readLock");
lock.readLock().lock();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " get readLock");
new Thread(new Runnable() {
	@Override
	public void run() {
		try {
			System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " try get readLock");
			lock.readLock().lock();
			System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " get readLock");
			Thread.sleep(2000);
			lock.readLock().unlock();
			System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " release readLock");
			Thread.sleep(1000);
			System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " try get readLock");
			lock.readLock().lock();
			System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " get readLock");
			lock.readLock().unlock();
			System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " release readLock");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}).start();
Thread.sleep(1000);
lock.readLock().unlock();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " release readLock");

System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " try get writeLock");
lock.writeLock().lock();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " get writeLock");
Thread.sleep(2000);
lock.writeLock().unlock();
System.out.println(format.format(new Date()) + " " + Thread.currentThread().getName() + " release writeLock");
Thread.sleep(10000);

运行结果:

2018-05-10 11:07:11 main try get readLock
2018-05-10 11:07:11 main get readLock
2018-05-10 11:07:11 Thread-1 try get readLock
2018-05-10 11:07:11 Thread-1 get readLock
2018-05-10 11:07:12 main release readLock
2018-05-10 11:07:12 main try get writeLock
2018-05-10 11:07:13 Thread-1 release readLock
2018-05-10 11:07:13 main get writeLock
2018-05-10 11:07:14 Thread-1 try get readLock
2018-05-10 11:07:15 main release writeLock
2018-05-10 11:07:15 Thread-1 get readLock
2018-05-10 11:07:15 Thread-1 release readLock

乐观锁/悲观锁

使用乐观锁和悲观锁,我个人理解其实关注点是对待一个可能出现竞争的问题的态度,如果我们认为这个操作只有少数情况下才会出现并发问题,那么使用一个乐观锁会更优。如果并发比较高,使用悲观锁就是更好。

乐观锁:乐观锁态度认为不加锁通常都不会有竞争,如果有竞争采用失败重试的方式循环操作直到成功,通常乐观锁采用一种原子更新+重试的方式,比如数据库增加版本号,通过update + where 版本号信息进行更新,如果update返回行数为0,则进行重试。

悲观锁:悲观锁态度认为竞争总是存在,所以每次都直接加锁进行操作。

偏向锁/轻量级锁/重量级锁

偏向锁/轻量级锁/重量级锁属于JVM锁优化的东西,和上面的锁不属于一个维度,我们在编程的时候无法察觉。

偏向锁:目的是在消除数据在无竞争的情况下的同步,从而提高系统的性能。在第一个线程获得到一个对象的锁时,在没有其他线程访问来获取锁之前,该锁是偏向锁,该线程不会同步数据到主存,减少了同步(加锁)的开销。在JVM启动参数增加-XX:+UseBiasedLocking会JVM会启动偏向锁,JDK1.6之后默认是开启的。

轻量级锁:目的是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。偏向锁是当只有一个线程使用一个锁的时候JVM进行的锁优化,但是当有另一个线程来获取锁的时候,偏向锁就不再有效,会膨胀成轻量级锁。轻量级锁是采用CAS将当前线程的栈帧中的信息和锁对象头中的信息交换来实现加锁,减少了互斥带来的开销。

重量级锁:偏向锁和轻量级锁都属于没有锁竞争的情况下的锁优化,一旦出现两个线程都需要同一个锁(发生了锁竞争),锁就会膨胀成重量级锁。

关于JVM的锁优化知识这里不详细介绍,还有自旋锁/锁消除/锁粗化等。关于锁优化的知识,有兴趣的可以阅读《深入理解Java虚拟机》周志明版的高效并发章节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值