JAVA锁相关

作者: 依弗布德甘 | 来源:发表于2020-01-12 11:49 被阅读0次

    Java锁的概念

    • 自旋锁
      循环抢锁,是指当一个线程在获取锁的时候,如果锁已经被其它线程抢占,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环

    • 乐观锁
      读取的时候不加锁,假定没有冲突,在修改数据时如果发现数据和之前获取的数据不一致,则退出或重新读取最新数据重试修改

    • 悲观锁
      从读取数据的时候就加锁,假设发生并发冲突,抢到锁后,才会同步所有对数据的相关操作

    • 独享锁(写)互斥
      同时只能有一个线程获得锁,一个线程获取到锁后,其他线程将会阻塞不会抢到锁。比如 ReentrantLock 是互斥锁

    • 共享锁(读)
      可以有多个线程同时获得锁,类似令牌池机制,可以定义同一时间能有多少线程抢到锁

    • 可重入锁、不可重入锁
      可重入锁:可多次加锁,不会阻塞。JDK提供的synchronized ,ReentrantLock都是可重入锁
      不可重入锁:第二次加锁后会阻塞线程

    • 公平锁、非公平锁
      公平锁:抢锁的顺序与抢到锁的顺序一致,会排队
      非公平锁:抢锁的顺序与抢到锁的顺序不一致

    自己实现锁-不可重入锁

    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.LockSupport;
    
    public class DemoLock implements Lock {
    
        //锁的拥有者
        AtomicReference<Thread> owner = new AtomicReference<>();
    
        //等待队列
        private LinkedBlockingQueue<Thread> waiter = new LinkedBlockingQueue<>();
    
    
        @Override
        public boolean tryLock() {
            return owner.compareAndSet(null, Thread.currentThread());
        }
    
        @Override
        public void lock() {
            if (!tryLock()){
                // 抢锁不成功,加入等待队列
                waiter.offer(Thread.currentThread());
                // 用死循环防止为唤醒问题
                while(true){
                    // 去除队列头部,但是不出队列
                    Thread head = waiter.peek();
                    // 判断是队列头部
                    if (head == Thread.currentThread()){
                        // 抢锁
                        if(!tryLock()){
                            //失败,挂起线程
                            LockSupport.park();
                        }else{
                            //抢锁成功,将线程出度列
                            waiter.poll();
                            return;
                        }
                    }else{
                        //不是头部,线程挂起
                        LockSupport.park();
                    }
                }
            }
        }
    
        @Override
        public void unlock() {
            if (tryUnlock()){
                Thread th = waiter.peek();
                if (th !=null){
                    LockSupport.unpark(th);
                }
            }
        }
    
        public boolean tryUnlock(){
            //首先判断当前线程是否站有锁
            if (owner.get() !=Thread.currentThread()){
                throw new IllegalMonitorStateException();
            }else{
                return owner.compareAndSet(Thread.currentThread(), null);
            }
        }
    
        @Override
        public void lockInterruptibly() throws InterruptedException {
    
        }
    
        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return false;
        }
    
        @Override
        public Condition newCondition() {
            return null;
        }
    }
    
    
    可重入锁和不可重入锁的区别
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantTest {
    
        private static  int i = 0;
    
        private final static Lock lc1 = new ReentrantLock();     //可重入锁
        private final static Lock lc2 = new DemoLock();   //不可重入锁
    
        public static void recursive() throws InterruptedException {
            lc1.lock();
            i++;
            System.out.println("here i am...");
            Thread.sleep(1000L);
            // 休眠一秒后重新执行该方法,lock不影响
            recursive(); 
    
            lc1.unlock();
        }
    
        public static void main(String args[]) throws InterruptedException {
            lc2.lock();
            System.out.println("加锁第一次。。。");
            lc2.lock();
            // 第二次加锁后,线程阻塞,不执行
            System.out.println("加锁第二次。。。");
    
            lc.unlock();
            lc.unlock();
    
            //recursive();
        }
    
    }
    

    同步关键字synchronized

    synchronized关键字通过修饰一个方法或声明一个代码块,从而产生一个同步对象锁以及对应的同步代码块,是通过锁对象的Monitor的取用与释放来修改其对象的头部信息,实现加锁解锁

    • Monitor(对象监视器)是内置于任何一个对象中
    // ObjectMonitor底层结构 C++代码
    ObjectMonitor::ObjectMonitor() {
      _header       = NULL;
      _count        = 0;
      _waiters      = 0,
      _recursions   = 0;
      _object       = NULL;
      _owner        = NULL;
      _WaitSet      = NULL;
      _WaitSetLock  = 0 ;
      _Responsible  = NULL ;
      _succ         = NULL ;
      _cxq          = NULL ;
      FreeNext      = NULL ;
      _EntryList    = NULL ;
      _SpinFreq     = 0 ;
      _SpinClock    = 0 ;
      OwnerIsThread = 0 ;
    }
    
    • synchronized 它的同步包括

      1. 对于普通方法同步,锁是当前实例对象
      2. 对于静态方法同步,锁是当前类的 Class 对象
      3. 对于方法块同步,锁是 Synchronized 括号里的对象
    • 特性:可重入锁,独享锁,悲观锁

    • synchronized的使用

    import com.study.lock.source.bak.ReentrantLock;
    import java.util.concurrent.locks.Lock;
    
    public class Demo {
        public static void main(String args[]){
            //如果你在实例方法上加锁,
            //多个线程,抢的是同一个所,还是多个锁
            //Counter ct1 = new Counter();
            //Counter ct2 = new Counter();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.staticUpdate();
                    //ct1.update();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Counter.staticUpdate();
                    //ct1.update();
                }
            }).start();
        }
    }
    
    
    class Counter{
    
        private static int i = 0;
     
        public synchronized void update() {
            //访问数据库
        }
        
        // 与update的写法语意一致
        public void updateBlock(){
            synchronized (this){
                //访问数据库
            }
        }
     
        public static synchronized void staticUpdate(){
            //访问数据库
        }
    
        // 与staticUpdate的写法语意一致
        public static void staticUpdateBlock(){
            synchronized (Counter.class){
                //访问数据库
            }
        }
    
        Lock lock = new ReentrantLock();
        public void updateReentrantLock(){
            lock.lock();
            try {
               //访问数据库
            }finally {
                lock.unlock();
            }
        }
    }
    
    
    • 锁优化
      锁消除: 在同一个线程里面,局部变量出现重复的加锁解锁,JIT编译之后,直接去掉了锁

      锁粗化: 在同一线程中,同一方法中出现重复加锁解锁,JIT编译会忽略多次加锁合并成一个锁

        public void test(Object arg) {
            // StringBuilder线程不安全,StringBuffer用了synchronized,是线程安全的
            // 局部变量,没有在其他线程中使用
            // 每次append都用了synchronized
            // jit 优化, 消除了锁
            StringBuffer stringBuffer = new StringBuffer();   
            stringBuffer.append("a"); 
            stringBuffer.append("b");
            stringBuffer.append("c");
     
            stringBuffer.append("a");
            stringBuffer.append("b");
            stringBuffer.append("c");
    
            System.out.println(stringBuffer.toString());
        }
    
        volatile int i = 0;
    
        public void test(Object arg) { 
            // 每次加锁都对 i 操作
            // jit 优化, 合并锁
            synchronized (this){
                i++;
                i--;
                //生成随机数  
            }
            synchronized (this){ 
                //生成随机数  
                i--;
            } 
        }
    

    synchronized 底层原理

    • 代码解析
    public class Demo {
        public static void main(String args[]){
            int a = 1;
            Teacher teacher = new Teacher();
            teacher.stu = new Student();
        }
    }
    
    class Teacher{
        String name = "james";
        int age = 40;
        boolean gender = true;
        Student stu;
    
        public void shout(){
        }
    }
    
    class Student{
        String name = "Emily";
        int age = 18;
        boolean gender = false;
    }
    
    • 代码在内存中如何存储
    内存中的存储方式
    • synchronized 加锁,其操作的就是 对象头部的 MarkWord
    轻量级锁,抢锁流程
    1. 两个线程抢锁之前,会先读取MarkWord的值
    2. 两个线程抢锁之前,会先读取MarkWord的值
    3. CAS操作会抢锁,只会有一个线程抢到锁
    4. 线程1抢到锁后,对象头部成为轻量级锁
    5. 线程2未抢到锁,将自旋
    6. 自旋一定程度,锁升级。CAS操作将把锁改为重量级锁
    • 重量级锁
      • 锁的升级过程:一旦对象锁升级为重级锁后,后续线程对该对象的操作都将是加锁为重量级锁
      • 锁不存在降级:锁只有从偏向锁->轻量级/重量级 或者 直接 轻量级-> 重量级. 不会降级,只有解锁
    重量级锁
    1. 线程1对当前对象抢锁成功后,对象onwer等于线程1。线程2自旋
    2. 自旋到一定程度,锁升级,线程2加入锁池,并持续抢锁
    3. 线程1调用wait()方法后,线程1释放锁,并加入WaitSet等待池中。onwer等于空
    4. 线程2抢锁成功,onwer等于线程2,线程2出entryList(锁池),执行线程2
    5. 线程2执行完毕,线程1.notify()方法,线程1退出等待池,onwer等于线程1
    6. 如果此时线程2又来抢锁,线程2加入锁池

    wait、notify和notifyAll方法是Object类的final native方法,这些方法不能被子类重写;
    wait只能在同步方法中调用notify和notifyAll只能在同步方法或同步块内部调用;

    • 偏向锁
      偏向锁是指,程序在编译过程中,只发现只有一个地方使用到(一个线程),会对该对象一直处于偏向锁的状态中,从而提高性能。一旦有其他线程来对该对象抢锁后,则锁升级
      如果需要,使用参数-XX:-UseBiasedLocking禁止偏向锁优化(默认打开)

    相关文章

      网友评论

        本文标题:JAVA锁相关

        本文链接:https://www.haomeiwen.com/subject/ivgdactx.html