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禁止偏向锁优化(默认打开)

相关文章

  • 2019大厂面试存疑

    Java相关 Java实现的锁有哪些synchornized实现的监视器锁、ReentrantLock、ReadW...

  • 偏向锁原理

    1 概述 本文介绍偏向锁相关原理,并不限定于Java中的偏向锁,但是Java中偏向锁的实现也是相同的原理,本文主要...

  • Java锁相关知识

    从ReentrantLock入手,学习Java锁相关知识 首先来看一下Java锁的使用 ReentrantLock...

  • Redis 分布式锁

    为什么需要分布式锁? 在传统单体应用单机部署的情况下,可以使用Java并发相关的锁,如ReentrantLcok或...

  • 利用Redis实现分布式锁

    为什么需要分布式锁? 在传统单体应用单机部署的情况下,可以使用Java并发相关的锁,如ReentrantLcok或...

  • java对象头及相关锁

  • java并发-独占锁与共享锁,公平锁与非公平锁,重入锁与非重入锁

    java并发-乐观锁与悲观锁,独占锁与共享锁,公平锁与非公平锁,重入锁与非重入锁 java 中的锁 -- 偏向锁、...

  • Java中的锁

    参考: java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁 Java中的锁--朱小厮 乐观锁和悲观锁 宏观...

  • Java中的锁

    Java中的锁 参考了这篇:Java中的锁分类 公平锁/非公平锁(ReentrantLock/Synchroniz...

  • 看完你就知道的乐观锁和悲观锁

    看完你就知道的乐观锁和悲观锁 Java 锁之乐观锁和悲观锁 [TOC] Java 按照锁的实现分为乐观锁和悲观锁,...

网友评论

    本文标题:JAVA锁相关

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