08:Java锁相关

锁:
     自旋锁: 为了判断某个条件是否成立,将代码写在While循环中。一直去判断这个条件。
             为了不放弃CPU的执行事件,循环使用CAS技术对数据进行尝试操作。
     悲观锁:假设会发生并发冲突,同步所有对数据的操作。
     乐观锁:假设没有发生冲突,在修改数据时如果发现版本号不一样了,重新读数据然后尝试修改。
     独享锁:将读数据和写数据分开锁。读是读的锁,写是写的锁。
         独享锁(写):给资源加写独享锁。同一时间只有一个线程可以写,其他线程都可以读。(单写,只有一个线程可以获取到写锁)
         独享锁(读):加上读锁后,只能读、不能写(多读,多个线程可以获取读锁)
     可重入锁:只要拿到第一把钥匙,后面的们都可以打开
     不可重入锁:每一个资源都需要获取新的钥匙
     公平锁:获取锁的机制是公平的,先到的线程先获取锁。
     非公平锁:后来的线程可能先获取到锁。
锁的概念和synchronized关键字:
    基于对象监视器实现的、基本的线程通信机制。Java中的每一个对象都于一个监视器关联,线程可以锁定或者解锁监视器。
    同步关键字不仅可以实现同步,根据JVM规范还可以保证可见性。(应为lock\unlock需要实现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机制实现)
    
 
  
 
 

猜你喜欢

转载自www.cnblogs.com/Xmingzi/p/12619768.html