java并发编程的各种锁
1.乐观锁
乐观锁并不是一种真正的锁,基于cas的思想,认为,并发操作不会修改数据的值。
适用于读比较多的场景(不阻塞线程,大大提效率)
2.悲观锁
悲观锁认为并发操作会对数据进行修改,认为并发操作不加实际的锁会出现错误,
适用于写比较多的操作
3.公平锁
在程序在并发操作时,一个加锁的线程工作,其他的线程会进入阻塞队列,当加锁的线
程运行结束阻塞队列的下一个线程获得cpu。(优先给排队时间最长的线程)
4非公平锁
非公平锁,当一个加锁线程在运行时,其他线程被阻塞,此线程运行结束后,其他所有线
程抢占cpu,抢到的获得cpu的运行权。
ReentrantLock 默认是非公平锁,但是底层可以通过 AQS 的来实现线程调度,所 以可以使其变成公平锁
1.AQS
当一个线程运行时,其他线程被阻塞在CLH同步队列(FIFO双向队列)里,AQS依靠让
CLH队列里有state属性,线程更改它获得锁对象
5.可重用锁
可重用锁为递归锁,当一个加锁的方法在运行,方法里又有另一个加锁的方法,那么这时
线程不会死锁,会运行里层的加锁方法。
例如;
package bingfa.writein;
/**
* @program: mianshi
* @ClassName Demo
* @description:
* @author: Mr.Yuan
* @create: 2021-07-21 16:39
**/
public class Demo {
public synchronized void setA(){
System.out.println("setA()开始");
setB();
System.out.println("setA()结束");
}
private synchronized void setB() {
System.out.println("setB()在运行");
}
}
测试类
package bingfa.writein;
/**
* @program: mianshi
* @ClassName Test
* @description:
* @author: Mr.Yuan
* @create: 2021-07-21 16:41
**/
public class Test {
public static void main(String[] args) {
Demo demo=new Demo();
demo.setA();
}
}
6.读写锁
ReadWriteLock
当线程在进行读操作的时候加读操作的锁
进行写操作的时候加写操作的锁
例如
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OrMr30sI-1626864566556)(C:\Users\57156\AppData\Roaming\Typora\typora-user-images\image-20210721163225470.png)]
特点
1.可以多个读者同时读
2.同一时间,只有一个写者可以运行
3.一旦有写者后续读者全部等待(唤醒时优先考虑写者)
7.分段锁
分段锁并非一种实际的锁,而是一种思想,用于将数据分段,并在每个分段上都会 单独加锁,把锁
进一步细粒度化,以提高并发效率
8.自旋锁
自旋锁:自旋锁是一种状态,并不是实际的锁,线程枪锁,枪锁失败后重试,抢到了就继续
抢不到就阻塞
特点:不阻塞线程提高效率,但过分消耗cpu
9.共享锁
可以被多个线程共同持有,并发进行访问。
例如:读写锁的读锁
10.独占锁
只可以被一个线程所访问,其他线程都会进入阻塞状态。
例如:synchronized,lock,ReentrantLock
11.4种锁状态
1.无状态锁
无状态所:没有锁
2.偏向锁:
偏向锁:只有一个线程访问锁对象时,会变成偏向锁,偏向锁会记住线程的id
下一次线程再次访问锁对象时,就会直接获取,提高效率(没有获取锁过程)。
3.轻量级锁
轻量级锁:当另一个线程访问锁对象,锁对象的状态此时为偏向锁,这个线程id
与偏向锁里存的线程id不同,此时锁对象的状态升为轻量级锁,线程通过不断自旋
的方式获取锁对象。(没有阻塞,效率相对较高,cpu耗费)
4.重量级锁状态
重量级锁:当锁对象的状态为轻量级锁,并发量增大,线程通过不断自旋获取锁对象
自旋超过一定次数却不能获得锁对象,这时锁对象的状态会变成重量级锁状态,让其他
没有获得锁对象的线程全部阻塞(减小cpu消耗)
12.Synchronized和ReentrantLock底层实现
1.Synchronized 是 由 JVM 实 现 的 一 种 实 现 互 斥 同 步 的 一 种 方 式,用Synchronized
修饰的代码块,在编译前前后加上指令monitorenter 和 monitorexit 两个字节码指令。
当运行到monitorenter ,如果对象没有锁,获取对象的锁,将锁的计数器加一
当运行到monitorexit时,如果对象有锁,则被阻塞。
代码演示
public class DieLock extends Thread{
static Object objA = new Object();
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
synchronized (objA){
System.out.println("if objA");
}
}
}
需要一个唯一的一个对象,用这个对象的对象头,来存状态
2.ReentrantLock
ReentrantLock 主要利用 CAS+AQS 队列来实现。它支持公平锁和非公平锁,
如果有三个线程,同时获取锁,如果一个线程获取到了,另外两个线程的状态就会
失败状态。
如果使用AQS则为公平锁
那么它会将获取失败的线程存入同步队列里,锁释放后,按出队顺序来获取锁
如果不使用公平锁
那么其他的线程阻塞,等锁释放后,各个线程继续获取锁(抢占)