JAVA中三种锁简要介绍

锁是多线程并发执行下,通过保证单位时间内只有单个线程对共享资源进行修改,从而保证程序能够正确执行。在Java语言中目前有这样三种锁。

  • synchronized
  • ReadWriteLock
  • StampedLock(java8)

synchronized

实现
  • Synchronized的语义底层是通过一个monitor的对象来完成,指令monitorenter和monitorexit标记线程获得和释放monitor(锁)。
优点
  • 写法简单, 通过synchronized 修饰方法或者代码块,Collections中通过synchronized实现了很多线程安全的集合类。
缺点
  • 重量级锁,线程锁切换需要消耗大量资源(cpu周期),javase 1.6有所优化。

ReadWriteLock

实现
  • 基于AbstractQueuedSynchronizer来实现,lock方法是基于比较并交换(CAS)。
优点
  • 读写分离,读锁共享,读读并行,读多写少场景性能高,并发包下打量工具类使用。
缺点
  • 可能会导致线程饥饿,取决于公平锁or非公平锁,公平锁不会饥饿,但是性能下降。

StampedLock

实现
  • 它使用了一个票据(stamp)的概念,这是一个long值,在加锁和解锁操作时,它被用作一张门票,解锁一个操作你需要传递相应的的门票。如果传递错误的门票,那么可能会抛出一个异常,或者其他意想不到的错误。StampedLock内部基于CLH锁,CLH是一种自旋锁,他能保证没有线程饥饿发生,并且保证FIFO顺序服务。
优点
  • 乐观读模式,乐观锁类似无锁操作,不会阻塞线程。
缺点
  • 使用较为复杂,类似cas操作重试使用的Unsafe.park()函数,park()函数遇到线程中断可能导致疯狂占用CPU情况。

测试性能

  • Synchronized、ReadWriteLock锁、StampedLock的读写锁以及读写乐观锁。读线程将读取一个计数器的值,写线程会将它从0增加到10000000。

Synchronized

public class Synchronized implements Counter
{
	private Object lock = new Object();
	
	private int counter;
	
	public long getCounter()
	{
		synchronized (lock)
		{
			return counter;
		}
	}
	
	public void increment() 
	{
		synchronized (lock)
		{
			++counter;
		}
	}
}

ReadWriteLock

public class RWLock implements Counter
{
	private ReadWriteLock rwlock = new ReentrantReadWriteLock();
	
	private Lock rlock = rwlock.readLock();
	private Lock wlock = rwlock.writeLock();
	
	private long counter;
	
	public long getCounter()
	{
		try
		{
			rlock.lock();		
			return counter;
		}
		finally
		{
			rlock.unlock();
		}
	}
	
	public void increment()
	{
		try
		{
			wlock.lock();		
			++counter;
		}
		finally
		{
			wlock.unlock();
		}
	}
}

StampedLock

public class Stamped implements Counter {

	private StampedLock rwlock = new StampedLock();
	
	private long counter;
	public long s, t;
	
	public long getCounter()
	{
		long stamp = rwlock.tryOptimisticRead();
		
		try
		{
			long result = counter;
			
			if (rwlock.validate(stamp))
			{
				return result;
			}
			
			stamp = rwlock.readLock();
			
			result = counter;
			
			rwlock.unlockRead(stamp);
			
			return counter;
		}
		finally
		{
			
		}
	}
	
	public void increment()
	{
		long stamp = rwlock.writeLock();
		
		try
		{	
			++counter;
		}
		finally
		{
			rwlock.unlockWrite(stamp);
		}
	}
}

结果

5个读线程和5个写线程
Using SYNCHRONIZED. threads: 10. rounds: 5. Target: 10000000
775
714
843
1027
1139

Using RWLOCK. threads: 10. rounds: 5. Target: 10000000
4769
8308
14998
11754
3735

Using STAMPED. threads: 10. rounds: 5. Target: 10000000
814
926
1210
1205
1265
10个读线程和10个写线程

Using SYNCHRONIZED. threads: 20. rounds: 5. Target: 10000000
959
776
919
1010
1187

Using RWLOCK. threads: 20. rounds: 5. Target: 10000000
5609
1193
5289
10819
16415


Using STAMPED. threads: 20. rounds: 5. Target: 10000000
856
808
842
946
1122

16个读线程和4个写线程
Using SYNCHRONIZED. threads: 20. rounds: 5. Target: 10000000
1839
3384
1909
2577
1943

Using RWLOCK. threads: 20. rounds: 5. Target: 10000000
55780
62211
68641
这里能说明读写锁这种状态效果不好

Using STAMPED. threads: 20. rounds: 5. Target: 10000000
6241
9457
6253
4048
12298

总结

总体看来, 整体性能表现最好的仍然是内置的同步锁。但是,这里并不是说内置的同步锁会在所有的情况下都执行得最好。这里主要想表达的是在你将你的代码投入生产之前,应该基于预期的竞争级别和读写线程之间的分配进行测试,再选择适当一个适当的锁。否则你会面临线上故障的风险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值