网站首页 > 技术教程 正文
是否可重入
可重入锁
可重入锁就是当前线程已经获取到了锁后,再次获取这个锁的时候,无需再次加锁,可以直接获取到这个锁资源(通过重入次数实现)
不可重入锁
当锁资源已被抢占后,即使相同的线程,在此获取锁的时候,也无法获取锁资源
我们在业务中使用的锁基本都是可重入锁,像synchronized,ReentrantLock都是可重入锁,那么锁为什么需要是可重入的呢,我们来看一个简单的例子
public class ReentryTest {
    public static void main(String[] args) {
        testA();
    }
    public static synchronized void testA() {
        System.out.println("execute method testA");
        testB();
    }
    public static synchronized void testB() {
        System.out.println("execute method testB");
    }
}如果synchronized不是可重入的会发生什么,执行方法testA,获取了ReentryTest.class对象的锁,这个时候调用方法testB,此时,testB也会获取ReentryTest.class的锁,这个时候就会发生死锁,因此必须是可重入的,否则,获得锁的线程,再次获取当前锁的时候,就会发生死锁
是否公平
公平锁
当有线程正在排队获取锁的时候,这个时候,新加入竞争锁的线程,会直接排队获取锁,保证先到的线程优先获取到锁
非公平锁
无论当前是否有线程在排队获取锁,新加入竞争锁的线程,都会去抢占锁,当锁获取失败后,才会排队获取锁
我们来看一下的公平锁和非公平锁的实现方式
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    final void lock() {
        acquire(1);
    }
    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 有线程正在排队获取锁,则不竞争锁,而是直接获取锁失败,加入同步队列进行排队
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        // 不管是否有排队获取锁的线程,直接尝试去获取锁
        return nonfairTryAcquire(acquires);
    }
}我们都知道,非公平锁比公平锁的性能更高,这是什么原因呢,公平锁,当有线程排队时,后续的线程都会调用操作系统底层的方法阻塞线程,在获取锁的时候,又需要调用操作系统的方法去唤醒线程,这些方法的性能都是比较低的,而非公平锁,通过抢占的模式,知道获取到了锁,就可以不用阻塞和唤醒线程,可以极大的提升性能,当然,非公平锁也会造成锁饥饿的问题,如果锁一直被抢占,就会造成之前排队的线程一直无法去抢占锁,造成锁饥饿
自旋锁
我们都听说在,在JDK1.6的时候,对synchronized进行了一系列的优化,其中包含偏向锁,轻量级锁,线程在获取轻量级锁的时候,并不是直接cas一次就去排队,而是会通过循环的形式去重复一定次数去获取锁,那么为什么需要自旋呢,这难道不消耗性能么,为了尽可能的提升性能,在很多时候,我们资源一定的次数,便可以获取到锁,而不需要将线程挂起,同时,控制自旋的次数,便可以防止CPU空执行的次数过多。
乐观锁
在大部分时候,数据并不会发生冲突,同时,通过重试还可以解决这种冲突,我们便可以使用乐观锁,通过不断地重试来解决冲突
读写锁
读读写锁就是包含读锁和写锁这两把锁,读锁和读锁之前不互斥,读锁和写锁,写锁和写锁之前互斥,通过这种形式,提升了读的并发性能,我们来看一下读写锁的使用方式
@Service
public class StudentService {
    @Autowired
    private StudentDao studentDao;
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    private Map<Long, Student> studentMap = new HashMap<>();
    public Student getStudent(Long id) {
        readLock.lock();
        try {
            Student student = studentMap.get(id);
            return student;
        } finally {
            readLock.unlock();
        }
    }
    @Scheduled(cron = "0 5 * * * ?")
    public void initCache() {
        writeLock.lock();
        try {
            List<Student> studentList = studentDao.findAll();
            Map<Long, Student> oldMap = studentMap;
            Map<Long, Student> map = studentList.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
            studentMap = map;
            oldMap.clear();
        } finally {
            writeLock.unlock();
        }
    }
}ReentrantReadWriteLock虽然提升了读的并发性能,但是读会阻塞写,导致写出现问题,因此,这种锁也会带来写饥饿的问题,我们来看一下一种更高性能的读写锁
@Service
public class StudentService {
    @Autowired
    private StudentDao studentDao;
    private StampedLock stampedLock = new StampedLock();
    private Map<Long, Student> studentMap = new HashMap<>();
    public Student getStudent(Long id) {
        // 使用乐观读锁
        long stamp = stampedLock.tryOptimisticRead();
        try {
            Student student = studentMap.get(id);
            // 读写锁出现冲突
            if (!stampedLock.validate(stamp)) {
                // 使用读锁,会阻塞写
                stamp = stampedLock.readLock();
                student = studentMap.get(id);
            }
            return student;
        } finally {
            stampedLock.unlockRead(stamp);
        }
    }
    @Scheduled(cron = "0 5 * * * ?")
    public void initCache() {
        long stamp = stampedLock.writeLock();
        try {
            List<Student> studentList = studentDao.findAll();
            Map<Long, Student> oldMap = studentMap;
            Map<Long, Student> map = studentList.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
            studentMap = map;
            oldMap.clear();
        } finally {
            stampedLock.unlockWrite(stamp);
        }
    }
}通过代码,我们发现StampedLock在读数据时,只会使用乐观锁,当发现冲突的时候,才会使用读锁,以此来解决读写冲突的问题,也就是说,在没有发生冲突的时候,读并不会阻塞写,通过这种形式,提升了读写的并发能力。
猜你喜欢
- 2024-10-30 读写锁,你难道不需要了解一下吗?
 - 2024-10-30 谷歌云故障14个小时,系“队列突变大量积压”引起
 - 2024-10-30 什么是AQS及其原理(aqs作用)
 - 2024-10-30 码仔漫画:怎么给女朋友讲明白线程池?
 - 2024-10-30 面试官:谈谈这4种磁盘IO调度算法--CFQ、NOOP、Deadline、AS
 - 2024-10-30 QT的信号槽机制简介(qt信号槽优缺点)
 - 2024-10-30 Java AQS(AbstractQueuedSynchronizer)详解
 - 2024-10-30 AQS是什么(AQS是什么药品)
 - 2024-10-30 详解磁盘IO调度算法--CFQ、NOOP、Deadline、AS
 - 2024-10-30 基于AbstractQueuedSynchronizer的并发类实现
 
欢迎 你 发表评论:
- 10-23Excel计算工龄和年份之差_excel算工龄的公式year
 - 10-23Excel YEARFRAC函数:时间的"年份比例尺"详解
 - 10-23最常用的10个Excel函数,中文解读,动图演示,易学易用
 - 10-23EXCEL中如何计算截止到今日(两个时间中)的时间
 - 10-2390%人不知道的Excel神技:DATEDIF 精准计算年龄,告别手动算错!
 - 10-23计算工龄及工龄工资(90%的人搞错了):DATE、DATEDIF组合应用
 - 10-23Excel中如何计算工作日天数?用这两个函数轻松计算,附新年日历
 - 10-23怎样快速提取单元格中的出生日期?用「Ctrl+E」批量搞定
 
- 最近发表
 - 
- Excel计算工龄和年份之差_excel算工龄的公式year
 - Excel YEARFRAC函数:时间的"年份比例尺"详解
 - 最常用的10个Excel函数,中文解读,动图演示,易学易用
 - EXCEL中如何计算截止到今日(两个时间中)的时间
 - 90%人不知道的Excel神技:DATEDIF 精准计算年龄,告别手动算错!
 - 计算工龄及工龄工资(90%的人搞错了):DATE、DATEDIF组合应用
 - Excel中如何计算工作日天数?用这两个函数轻松计算,附新年日历
 - 怎样快速提取单元格中的出生日期?用「Ctrl+E」批量搞定
 - Excel日期函数之DATEDIF函数_excel函数datedif在哪里
 - Excel函数-DATEDIF求司龄_exceldatedif函数计算年龄
 
 
- 标签列表
 - 
- 下划线是什么 (87)
 - 精美网站 (58)
 - qq登录界面 (90)
 - nginx 命令 (82)
 - nginx .http (73)
 - nginx lua (70)
 - nginx 重定向 (68)
 - Nginx超时 (65)
 - nginx 监控 (57)
 - odbc (59)
 - rar密码破解工具 (62)
 - annotation (71)
 - 红黑树 (57)
 - 智力题 (62)
 - php空间申请 (61)
 - 按键精灵 注册码 (69)
 - 软件测试报告 (59)
 - ntcreatefile (64)
 - 闪动文字 (56)
 - guid (66)
 - abap (63)
 - mpeg 2 (65)
 - column (63)
 - dreamweaver教程 (57)
 - excel行列转换 (56)
 
 

本文暂时没有评论,来添加一个吧(●'◡'●)