一、背景介绍
1、在进程中的同时运行多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,
线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
2、在Java并发编程中,经常遇到多个线程访问同一个 共享资源 ,开发者必须考虑如何维护数据一致性,这就是Java锁机制(同步问题)的来源。
二、四种锁
1、synchronized 维护数据一致性、 在资源竞争不是很激烈的情况下 、可读性好
synchronized实现的机理依赖于软件层面上的JVM,随着Java版本的不断升级而提高,1.6之后对其进行了很多优化(自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁)
注意:当线程通过synchronized等待锁时是不能被Thread.interrupt()打断的,所以设计时必须检查,防止死锁发生。在确定锁机制是当前多线程程序的性能瓶颈时,可考虑虑使用其他机制,如ReentrantLock等。
2、ReentrantLock 可重入锁 锁可以被线程多次重复进入进行获取操作。 高并发量情况下使用ReentrantLock
1)ReentrantLock 继承接口Lock 并实现了接口中定义的方法,除了能完成synchronized的工作外,还提供了响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
2)Lock实现的机理依赖于特殊的CPU指定,可以认为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,
但高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准。
3)公平锁:锁的分配是公平的, 先对锁提出请求的线程会被分配到锁
4)非公平锁:JVM 按随机、就近原则分配锁的机制。
5)ReentrantLock在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。因为非公平锁实际执行的效率远远超过公平锁。
6)ReentrantLock通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized会被JVM自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁。在finally 控制块中执行解锁操作。
public static void main(String[] args) {
Lock lock = new ReentrantLock();
/**
* 方式一
*/
assert lock.tryLock(); //判断是否获取锁
//事务操作
lock.unlock();
/**
* 方式二
*/
try {
lock.lock();
//事务操作
} finally {
lock.unlock();
}
}
3、Semaphore
1)互斥是进程同步关系的一种特殊情况,相当于只存在一个临界资源,因此同时最多只能给一个线程提供服务。在实际复杂的多线程应用程序中,可能存在多个临界资源,
这时候我们可以借助Semaphore信号量来完成多个临界资源的访问。
2)Semaphone.acquire()方法默认为可响应中断锁,与ReentrantLock.lockInterruptibly()作用效果一致,也就是说在等待临界资源的过程中可以被Thread.interrupt()方法中断。
除了方法名不同之外,使用方法大致相同。Semaphore也提供了公平与非公平锁的机制
3)释放锁的操作也必须在finally代码块中完成。通过acquire()与release()方法来获得和释放临界资源。
4.AtomicInteger
1)首先说明,此处AtomicInteger是一系列相同类的代表之一,常见的还有AtomicLong、AtomicLong等,他们的实现原理相同,区别在与运算对象类型的不同。通过相关资料显示,通常AtomicInteger的性能是ReentantLock的好几倍。
2不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,
一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。