多线程学习——锁的分类

多线程学习——锁的分类

Scroll Down

多线程学习——锁的分类

1.线程要不要锁住同步资源——(悲观锁和乐观锁)

1.悲观锁和乐观锁的概念

悲观锁:互斥同步锁,总是使用资源的时候锁住 synchronized和lock接口
乐观锁:非互斥同步锁,在修改资源的时候检验 原子类和并发容器

2.互斥同步锁的劣势

1.阻塞和唤醒性能的劣势
2.永久阻塞问题
3.优先级反转:如果给线程设置了优先级,低优先级的拿到了锁不释放,就会造成优先级反转	

3.使用场景

悲观锁:

适合并发写入多的情况,适用于临界区持锁时间比较长的情况,悲观锁可以避免大量无用自旋等消耗,典型情况:
1.临界区有IO操作
2.临界区代码复杂或者循环量大
3.临界区竞争非常激烈

乐观锁:

适合并发写入少,大部分是读取的情况

2.可重入锁和非重入锁

以ReentranLock为例:
可重入:获取到锁就不用在释放锁去竞争锁就可以直接使用的锁,可以重入使用一把锁
可重入的好吃:避免死锁,提高了封装性
可重入锁的用处:可以递归处理需要N次操作的资源
可重入锁的原理:获取锁先进行判断,如果当前线程就是占有锁的线程,则status+1,并返回true,释放锁是先判断当前线程就是占有锁的线程,如果status=0,才真正释放锁
image.png
不可重入锁:每次使用同一把锁都需要去重新区获取。
不可重入锁的原理:获取锁是直接尝试获取锁,而释放锁是直接将status=0

image.png

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();
        }

    }


}

公平锁效果:
image.png
非公平锁效果:
image.png

公平锁 各线程公平平等,每个线程在等待一段时间后 更慢,吞入量更小
总有执行的机会
非公平锁 更快,吞吐量更大 有可能产生线程饥饿,线程长时
间的得不到执行

公平锁原理:
image.png
非公平锁原理:
image.png

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.根据不同的业务场景类型选择锁

祝君好梦!