• 08:Java锁相关


    锁:
         自旋锁: 为了判断某个条件是否成立,将代码写在While循环中。一直去判断这个条件。
                 为了不放弃CPU的执行事件,循环使用CAS技术对数据进行尝试操作。
         悲观锁:假设会发生并发冲突,同步所有对数据的操作。
         乐观锁:假设没有发生冲突,在修改数据时如果发现版本号不一样了,重新读数据然后尝试修改。
         独享锁:将读数据和写数据分开锁。读是读的锁,写是写的锁。
             独享锁(写):给资源加写独享锁。同一时间只有一个线程可以写,其他线程都可以读。(单写,只有一个线程可以获取到写锁)
             独享锁(读):加上读锁后,只能读、不能写(多读,多个线程可以获取读锁)
         可重入锁:只要拿到第一把钥匙,后面的们都可以打开
         不可重入锁:每一个资源都需要获取新的钥匙
         公平锁:获取锁的机制是公平的,先到的线程先获取锁。
         非公平锁:后来的线程可能先获取到锁。
    锁的概念和synchronized关键字:
        基于对象监视器实现的、基本的线程通信机制。Java中的每一个对象都于一个监视器关联,线程可以锁定或者解锁监视器。
        同步关键字不仅可以实现同步,根据JVM规范还可以保证可见性。(应为lockunlock需要实现Happens-before原则)
        锁的范围:类锁、对象锁、锁消除、锁粗化。(这些关键字都是在文档:Java SE 6 Performance White Pater 中找到的。)
        对象锁:
            public synchronized void test(){}
        类锁:
            public synchronized static void test(){} // 加了static关键字
        锁粗化(运行时JIT编译优化):由于多段代码用到了同一个锁,编译时:将者写锁的粒度粗化,同步代码块放大,将这些代码段包含。
            public void test(){
                 synchronizedthis){A段}
                 synchronizedthis){B段}
            }
            优化后:
             public void test(){
                 synchronizedthis){A段,B段}
             }
        锁消除:(运行是JIT优化):将锁取消。
            例如:StringBuilder是线程安全的,每一个append方法中都有锁。JIT对含有append的热点代码去掉了锁。
            (JIT觉得没有线程安全问题的时候才会优化,临界区内没有竞争条件。)
        synchronized锁的实现原理:
            JVM对锁的优化会经历:偏向锁、轻量级锁、重量级锁。
             偏向锁:(优化后的锁机制,默认开启的)
                 1:锁对象中有个标志位,表示是否开启了偏向锁。还有个位置存放线程ID,默认值为0。
                 2:线程来了判断是否是对象是否是偏向锁,如果是,接着判断锁对象中的线程ID是否为0,如果为0:可用,然后将其改成自己的线程ID。
                 3:如果只有一个线程,其实就是无锁。应为锁ID的位置一直是一个线程ID。
                 其实就是判断锁对象中的线程ID是否是有线程ID,以此判断锁对象是否被使用。
                 如果多个线程争抢含有偏向锁的锁:
                     第一个线程修改锁对象的线程ID后
                     第二个线程来了以后判断对象锁的线程ID已经被写过了:出现了争抢锁的情况。
                     这个时候锁机制改成轻量级锁。使用CAS机制判断锁对象的锁标志位(自旋一定此处后进入阻塞,因为很消耗资源)。
            轻量级锁:
                1:判断存放对象的内存空间中的某一个位置的状态,这个位置标着了该对象是否加锁。我们简称为锁标志位
                2:一个线程获取到该对象的锁时:CAS机制判断对象中的锁标志位,如果成功,修改对象锁标志位、线程栈中存储该对象的锁信息。
                其实就是每个线程来了都要同步代码块中是否有别的线程正在使用。(判断锁对象的锁标志位)
            重量级锁-(监视器锁)
                CAS自旋n次后如果还没有获取到锁,锁会升级为重量级锁,进入阻塞。
                monitor也叫做管程,一个对象对应一个monitor。存放了等待该线程的队列等。以此来实现对锁的等待、争抢、释放。
    
    
    其他:
        事务中调用费事的接口。事务开始时会和数据库建立连接,如果这个时候做一些费事的事情。会使数据库连接时间过长。
    
    Lock接口的使用:
        lock 获取锁,如果锁已经被别的线程占用,进入等待。线程在等待的过程中被中断了,报错。
        lockInterruptibly 获取锁,如果已被人占用,进入等待。线程在等待的过程中被中断了,抛出异常、结束等待。
        tryLock 尝试获取锁,立即返回获取到或者获取不到。
        unlock 释放锁
    ReentrantLock:可重入锁
        独享锁;支持公平锁、非公平锁两种模式。
        掉用了几此lock。就需要调用几次unlock。
    class test0{
        private final ReentrantLock lock = new ReentrantLock();
        void m(){
            lock.lock();
            try{
                x();
            }finally {
                lock.unlock();
            }
        }
        void x(){
            lock.lock();
            // TODO 执行逻辑
            lock.unlock();
        }
    }
    ReadWriteLock:读写锁
        维护了两个锁,读锁、写锁。
        读锁可以被多个线程获取,写锁只能有一个线程获取。存在读锁将获取不到写锁。
        HashTable就是使用了同步关键字,读和写都只有一个线程可以获取。如果是读多写少性能就比较差了,但是线程安全的。被concurrentHashMap取代。
    class test1{
        int i = 0;
        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        public void main(String[] args) {
            new Thread(() -> read()).start();
            new Thread(() -> read()).start();
            new Thread(() -> write()).start();
        }
        // 多个线程可以获取读锁
        public void read(){
            readWriteLock.readLock().lock();
            //
            readWriteLock.readLock().unlock();
        }
        // 只有一个线程可以获取写锁
        public void write(){
            readWriteLock.writeLock().lock();
            //
            readWriteLock.writeLock().unlock();
        }
    }
        锁降级:
            写锁降级为读锁。把持住当前的写锁的同时,获取读锁,然后释放写锁。(可以保证数据不会被多次修改,原因:有读锁的时候写锁不会被获取到)
            场景:缓存中间件
                  读锁中先判断缓存,缓存中没有的化去DB中获取数据。
                  如果大量的线程去读锁,则会有会大量的线程查询DB。缓存雪崩。
                  解决:释放读锁,开启写锁。查数据库。
    class test2{
        // 创建一个map拥于缓存数据。
        private Map<String , Object> map = new HashMap<>();
        // 使用可重入的读写锁
        private static ReadWriteLock rwl = new ReentrantReadWriteLock();
        public Object get(String id){
            Object value = null;
            // 首先开启读锁,从缓存中去取
            rwl.readLock().lock();
            try{
                if(map.get(id) == null){
                    // 必须释放读锁
                    rwl.readLock().unlock();
                    // 查询数据库,为了避免大量的线程去查询数据库。这里先使用写锁锁住(其他的读写线程都进不来了,赢得了读数据库的时间)。
                    rwl.writeLock().lock();
                    try{
                        // 会有多个读线程都被挡在写锁之外,为了保证只查询一次数据库。再次判断一次是否有别的线程已经查询过了。
                        if(map.get(id) == null){
                            // TODO 读数据库。然后将结果写入到Map中缓存。
                        }else {
                            value = map.get(id);
                        }
                        // 将写锁降级为读锁,这样就不会有别的线程能够修改这个值了。保证数据唯一性。(存在读锁的时候获取不到写锁)
                        rwl.readLock().lock();
                    }finally {
                        rwl.writeLock().unlock();
                    }
                }else {
                    value = map.get(id);
                }
            }finally {
                rwl.readLock().unlock();
            }
            return value;
        }
    }
    Condition机制:
        用于替代wait/notify
        Object中的wait(),notify(),notifyAll(),配合synchronized使用。可以唤醒一个多所有线程。无法准确的唤醒具体的线程。
        Condition需要配置Lock使用,提供多个等待集合、更加精准的控制唤醒(底层使用park/unpark机制实现)
        
    
    
    
    
    
  • 相关阅读:
    kubernetes之常见故障排除(一)
    kubernetes集群管理命令(三)
    kubernetes集群管理命令(二)
    kubernetes集群管理常用命令一
    kubernetes集群管理之通过jq来截取属性
    kubernetes管理之使用yq工具截取属性
    kubectl技巧之通过jsonpath截取属性
    kubectl技巧之通过go-template截取属性
    kubernetes容器编排之定义环境变量以及通过downwardapi把pod信息作为环境变量传入容器内
    kubectl技巧之查看资源列表,资源版本和资源schema配置
  • 原文地址:https://www.cnblogs.com/Xmingzi/p/12619768.html
Copyright © 2020-2023  润新知