一、复习
二、公平锁与非公平锁
- 按照线程请求并获得锁的时间顺序,可以将锁分为公平锁和非公平锁
- 公平锁:线程获取锁的顺序是按照线程请求锁的时间早晚来进行划分的,也就是满足先到先得的原则;
- 非公平锁:线程在运行时闯入的,并不是按照先到先得的原则。
1.Java中两种锁的实现机制
- Reentrant reentrant = new Reentrant(true)代表公平锁
- Reentrant reentrant = new Reentrant(false)代表非公平锁
- 在没有公平性需求的前提下尽量使用非公平锁,因为公平锁会带来额外的开销。
三、独占锁和共享锁
- 按照一个资源是否可以同时被多个线程持有,或者只能被一个线程持有,可以分为独占锁和共享锁。ReetrantLock锁是一个独占锁,同一时间只能由一个线程所持有;共享锁可以由多个线程共同 持有,比如:ReadWriteLock.
- 独占锁是一中悲观锁,这种必须先加排他锁才能对资源进行访问,限制了并发性;共享锁是一中乐观锁,这种放宽了加锁的条件,允许多个线程能够同时访问资源。
四、可重入锁
- 定义:当一个线程要获取一个被其他线程占有的独占锁时,该线程会被阻塞,那么当一个线程获取它自己已经获取的锁时是否会被阻塞起来呢?如果不会阻塞就可以称为可重入锁
package com.ruigege.PricipleAnalyzingOfThreadLocalRandom3;
public class Hello {
public static void main(String[] args) {
new Hello().helloB();
}
public synchronized void helloA() {
System.out.println("HelloA");
}
public synchronized void helloB() {
System.out.println("HelloB");
helloA();
}
}
- 代码解析:hellB方法调用,会先获取得内置锁,然后打印输出,之后调用helloA方法,在调用之前会先获取内置锁,如果内置锁不是可重入的,那么调用线程将会一直阻塞。
- 实际上synchronized内部锁是一个可重入锁,可重入锁的原理是在锁的内部维护一个线程标示,用于标示该锁目前正在被哪个线程占用,然后关联一个计数器,一开始计数器值为0,说明该锁没有被任何线程占用,当一个线程获取了该锁时,计数器的值会变成1,计数器的值会变成1,这时其他线程再来获取该锁时会发现锁的所有者不是自己而被阻塞挂起。但是当获取了该锁的线程再次获得锁的时候发现锁的拥有者时自己,就会把计算器值+1,当释放锁后计数器-1,当计数器为0的时候,锁里面的线程标示被重置为null,这时候被阻塞的线程会被唤醒来竞争获取该锁。
五、自旋锁
- 由于Java中的线程是与操作系统中的线程一一对应的,所以当一个线程在获取锁(比如独占锁)失败后,会被切换到内核状态而被挂起,当该线程获取到锁时又需要将其切换到内核状态而唤醒该线程。而从用户状态切换到内核状态的开销是比较大的,在一定程度上会影响并发性能,自旋锁则是,当前线程在获取锁的时候,如果发现这个锁已经被其他的锁占用了,他不能马上阻塞自己,在不放弃CPU使用权的情况下,多次尝试获取(默认次数10,可以使用-XX:PreBlockSpinsh参数设置该值),很有可能在后面几次尝试中其他线程已经释放了锁,如果尝试指定的次数后仍没有获取到锁则当前线程才会被阻塞挂起,由此看来自旋锁时使用了CPU时间获取线程阻塞与调度的开销,但是很有可能这些CPU时间白白浪费了。
六、源码:
- 所在包:com.ruigege.OtherFoundationOfConcurrent2
https://github.com/ruigege66/ConcurrentJava
- 欢迎关注微信公众号:傅里叶变换,个人账号,仅用于技术交流