Java多线程---锁的种类都在这

java锁分类   ---原文

1、悲观锁 / 乐观锁

悲观锁 :
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁:
Lock用的是乐观锁方式,每次去拿数据的时候都认为别人不会修改不上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,使用版本号机制和CAS算法实现。

两种锁的使用场景
乐观锁适用于多读场景,省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。


乐观锁常见的两种实现方式
1. 版本号机制
在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若当前最后更新的version与操作员第一次的版本号是否相等时才更新,否则重试更新操作,直到更新成功。

2. CAS算法
即compare and swap,是一种有名的无锁算法。就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步。
1、需要读写的内存值 V
2、进行比较的值 A
3、拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作。一般情况下是一个自旋操作,即不断的重试。

ReentrantLock的高级操作

ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了定时锁等候和中断锁等候。
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定
1、如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
2、如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

synchronized和lock用途区别
在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。

1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候

ReentrantLock获取锁定有三种方式

1、lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
2、tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false
3、tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
4、lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到获取锁定,或者当前线程被别的线程中断

synchronized和lock的区别


1、lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断


2、阻塞和自旋锁

自旋是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断判断是否能够被成功获取,自旋直到获取到锁才会退出循环。自旋是通过CAS算法进行的。

3、无锁、偏向锁、轻量级锁、重量级锁

随着竞争不断的加剧,锁要不断的升级。

(1)无锁:适用于单线程
(2)偏向锁:适用于只有一个线程访问同步块的情况,因为多个线程同时访问同步块,给某一个线程特权是不合理的
(3)轻量级锁:竞争不是太多,循环等待消耗CPU资源的线程的数量在可接受的范围
(4)重量级锁:多个线程同时竞争资源,只让一个线程运行,其余的线程都阻塞
 

*注意为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

这几种锁的优缺点(偏向锁、轻量级锁、重量级锁)

 

4、公平锁和非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Synchronized和ReentrantLock而言,是一种非公平锁。但是ReentrantLock通过构造函数指定该锁是公平锁,通过AQS的来实现线程调度,AQS由三个部分组成
State:当前线程锁的个数
exclusiveOwerThread:当前占有锁的线程 
CLH队列等待运行的线程。

1 、线程1 CAS算法A=V(state)=0,修改state的值为1
2、 线程1又想获取锁,此时A=V(state)=1,state再加1,无论A想获得多少次,只是state+1
3 、线程2 进行CAS比较,发现A不等于V,并且发现state不等于0,直接到CLH列队中等待。线程3和线程4也一样到CLH队列中等待。如果先来的线程先排队,获取锁的优先权,则为公平锁。如果,无视等待队列,直接尝试获取锁,则为非公平锁。

队列如果已经满了,该怎么办呢?
无法进入队列的线程,进入ArrayBlockingQueue,等队列有空位再进入队列


5、可重入锁和不可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Reentrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

public sychrnozied void test() {
    xxxxxx;
    test2();
}

public sychronized void test2() {
    yyyyy;
}

在上面代码段中,执行 test 方法需要获得当前对象作为监视器的对象锁,但方法中又调用了 test2 的同步方法。

1、如果锁是具有可重入性的话,那么该线程在调用 test2 时不需要再次获得当前对象的锁,可以直接进入 test2 方法进行操作。

2、如果锁是不具有可重入性的话,那么该线程在调用 test2 前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次获得。

如果锁是不具有可重入性特点的话,那么线程在调用同步方法、含有锁的方法时就会产生死锁。


6、互斥锁和共享锁

互斥锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是互斥锁。对于Synchronized而言,当然是互斥锁
但是对于Lock的另一个实现类ReentrantReadWriteLock,其读锁是共享锁,其写锁是互斥锁。读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。互斥锁与共享锁也是通过AQS来实现的,通过实现不同的方法实现独享或者互斥

你可能感兴趣的:(进阶类)