多线程学习——锁的分类
1.线程要不要锁住同步资源——(悲观锁和乐观锁)
1.悲观锁和乐观锁的概念
悲观锁:互斥同步锁,总是使用资源的时候锁住 synchronized和lock接口
乐观锁:非互斥同步锁,在修改资源的时候检验 原子类和并发容器
2.互斥同步锁的劣势
1.阻塞和唤醒性能的劣势
2.永久阻塞问题
3.优先级反转:如果给线程设置了优先级,低优先级的拿到了锁不释放,就会造成优先级反转
3.使用场景
悲观锁:
适合并发写入多的情况,适用于临界区持锁时间比较长的情况,悲观锁可以避免大量无用自旋等消耗,典型情况:
1.临界区有IO操作
2.临界区代码复杂或者循环量大
3.临界区竞争非常激烈
乐观锁:
适合并发写入少,大部分是读取的情况
2.可重入锁和非重入锁
以ReentranLock为例:
可重入:获取到锁就不用在释放锁去竞争锁就可以直接使用的锁,可以重入使用一把锁
可重入的好吃:避免死锁,提高了封装性
可重入锁的用处:可以递归处理需要N次操作的资源
可重入锁的原理:获取锁先进行判断,如果当前线程就是占有锁的线程,则status+1,并返回true,释放锁是先判断当前线程就是占有锁的线程,如果status=0,才真正释放锁
不可重入锁:每次使用同一把锁都需要去重新区获取。
不可重入锁的原理:获取锁是直接尝试获取锁,而释放锁是直接将status=0
3.公平锁和非公平锁
以ReentranLock为例:
非公平锁:在锁的获取的时候 允许空档期(即线程唤醒的时候)插队,ReentranLock就是非公平锁
例如:
package cn.xxx.practice.mylock.fairlock;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @ClassName: FairLock
* @Description: 演示公平锁 与非公平锁
* @author: XP
*/
public class FairLock {
public static void main(String[] args) {
PrintQueen printQueen = new PrintQueen();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i]=new Thread(new RunPrint(printQueen));
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
}
}
class RunPrint implements Runnable{
private PrintQueen queen;
public RunPrint(PrintQueen queen) {
this.queen = queen;
}
@Override
public void run() {
System.out.println(Thread.currentThread()+"开始打印任务");
queen.printWork();
System.out.println(Thread.currentThread()+"结束打印任务");
}
}
class PrintQueen{
public static Lock lock= new ReentrantLock(false);
public void printWork(){
lock.lock();
try {
int durant=new Random().nextInt(9)+1;
System.out.println(Thread.currentThread()+"开始打印a");
Thread.sleep(durant);
}catch (Exception e){
}
finally {
lock.unlock();
}
lock.lock();
try {
int durant=new Random().nextInt(9)+1;
System.out.println(Thread.currentThread()+"开始打印b");
Thread.sleep(durant);
}catch (Exception e){
}
finally {
lock.unlock();
}
}
}
公平锁效果:
非公平锁效果:
公平锁 各线程公平平等,每个线程在等待一段时间后 更慢,吞入量更小
总有执行的机会
非公平锁 更快,吞吐量更大 有可能产生线程饥饿,线程长时
间的得不到执行
公平锁原理:
非公平锁原理:
4.共享锁和排他锁
以reentrantreadwritelock为例
排他锁:独占锁,独享锁
共享锁:读写锁,获得共享锁之后,可以查看但无法修改和删除数据,其他数据此时也可以获取共享锁,也可以查看但也无法修改数据(reentrantreadwritelock)
读写锁的规则:
1.多个线程都只申请读锁,是可以申请到的
2.如果一个线程已经占用到写锁,则此时其他线程如果要申请写锁,则需要等待占用写锁的线程释放
3.如果一个线程已经占用到写锁,其他线程申请写锁或者读锁,则需要等待占用写锁的线程释放
只有可能多读,不可能一边读一边写
读锁插队策略:
公平锁:不允许插队
非公平锁:写锁可以随时插队 读锁仅在等待列头节点不是想获取写锁的线程的时候可以插队
升降级策略:
写锁是可以降级为读锁 但是读锁不能升级为写锁
5.自旋锁和阻塞锁
java.util.concurrent.atomic包基本都是自旋锁
自旋锁:让当前线程自旋,自旋完成后获取资源,不用切换线程,就是一直去检测锁,不去阻塞线程
阻塞锁:没有获取当所资源就阻塞,直到被唤醒
自旋锁缺点:如果锁被占用的时间长,那么自旋的线程就只会白浪费处理起资源,在自旋的过程中,一直占用CPU,随着自选时间的增长,开销也增长
6.可中断锁和不可中断锁
可中断锁:ReentranLock
不可中断锁: synchronized
6.java虚拟机对锁的优化
自旋锁和自适应:自适应是自旋锁通过尝试次数来完成的
锁消除:jvm会判断是否需要锁,不需要锁的自动消除
锁粗化
7.总结
1.缩小同步代码块,只锁共享资源。
2.尽量不要锁住方法
3.尽量减少请求锁的次数
4.减少共享数据
5.锁中不要再包含锁
7.根据不同的业务场景类型选择锁
祝君好梦!