Java中的锁

简介: Java中的锁

悲观锁


认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。


synchronized关键字和Lock的实现类都是悲观锁


适用场景:适合写操作多的场景,先加锁可以保证写操作时数据正确。


乐观锁


认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。

在Java中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。


如果这个数据没有被更新,当前线程将自己修改的数据成功写入。

如果这个数据已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等


判断规则


版本号机制Version

最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

适用场景:适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升


synchronized


一个对象里如果有多个synchronized方法,某一时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一时刻内,只能有唯一的一个线程去访问这些synchronized方法。


锁的是当前对象this,被锁定后,其他的线程都不能进入到当前的对象的其他synchronized方法


类中的普通方法和同步锁方法无关

new 两个对象后,就不是同一把锁了


类中的静态同步方法

对于普通同步方法,锁的是当前实例对象,通常指this,具体的一个个new Class(); 所有的普通同步方法用的都是同一把锁:实例对象本身


对于静态同步方法,锁的是当前类的Class模板,Class模板只有一个,一个Class模板可以new出多个Class对象


对于同步方法块, 锁的是synchronized括号内的对象


当一个线程试图访问同步代码时它必须得到锁,正常退出或抛出异常时必须释放锁。

所有的同步方法用的都是同一把锁–实例对象本身,就是new出来的具体实例对象本身,本类this

也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁


所有的静态同步方法用的也是同一把锁–类对象本身,就是唯一模板Class

具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法和普通同步方法之间是不会有竟态关系的

但是一旦一个静态同步方法获取锁后,其他的静态同步方法必须等待该方法释放锁后才能获取锁。


为什么任何一个对象都可以成为一个锁?


每个对象天生都带着一个对象监视器(objectMonitor),每一个被锁住的对象都会和Monitor关联起来


公平锁


指多个线程按照申请锁的顺序来获取锁,类似排队买票,先来的人先买后来的人在队尾排着,这是公平的

Lock lock = new RenntrantLock(true);  //true表示公平锁,先来先得


非公平锁


值多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级反转或者饥饿的状态(某个线程一直得不到锁)

Lock lock = new RenntrantLock(false); //false表示非公平锁,后来的也可能先获得锁
Lock lock = new RenntrantLock(); // 默认非公平锁


为什么会有公平锁/非公平锁的升级?默认非公平锁?


恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片,尽量减少CPU的空闲状态时间。

使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销

如果为了更高的吞吐量,很显然非公平锁比较合适,因为节省很多线程切换时间,吞吐量自然就上去了。

否则就用公平锁,公平使用,雨露均沾


可重入锁


又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。


如果是1个有synchronized修饰的递归调用方法,程序第二次进入被自己阻塞了那岂不是无解了

所以Java中RenntrantLock和synchronized都是可重入锁,可重入锁的一个优点是可以定成都避免死锁

1673407777387.jpg


隐式锁


//同步代码块
final Object object = new Object();
        new Thread(() -> {
            synchronized (object){
                System.out.println(Thread.currentThread().getName()+"\t ----外层调用");
                synchronized (object){
                    System.out.println(Thread.currentThread().getName()+"\t ----中层调用");
                    synchronized (object){
                        System.out.println(Thread.currentThread().getName()+"\t ----内层调用");
                    }
                }
            }
        },"t1").start();
//同步方法
public class ReEntryLockDemo{  
    public synchronized void m1()
        {
            //指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
            System.out.println(Thread.currentThread().getName()+"\t ----come in");
            m2();
            System.out.println(Thread.currentThread().getName()+"\t ----end m1");
        }
        public synchronized void m2()
        {
            System.out.println(Thread.currentThread().getName()+"\t ----come in");
            m3();
        }
        public synchronized void m3()
        {
            System.out.println(Thread.currentThread().getName()+"\t ----come in");
        }
    public static void main(String[] args)
    {
        ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
        new Thread(() -> {
            reEntryLockDemo.m1();
        },"t1").start();
    }
}


指的是可重复可递归调用的锁,在外层使用锁之后,在内存仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。

简答的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的


显示锁

static Lock lock = new ReentrantLock();
    public static void main(String[] args)
    {
        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println(Thread.currentThread().getName()+"\t ----come in外层调用");
                lock.lock();
                try
                {
                    System.out.println(Thread.currentThread().getName()+"\t ----come in内层调用");
                }finally {
                    lock.unlock();
                }
            }finally {
                // 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
                //lock.unlock();// 正常情况,加锁几次就要解锁几次
            }
        },"t1").start();
        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println(Thread.currentThread().getName()+"\t ----come in外层调用");
            }finally {
                lock.unlock();
            }
        },"t2").start();
    }


死锁


死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法继续推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

1673407837280.jpg

public static void main(String[] args)
    {
        final Object objectA = new Object();
        final Object objectB = new Object();
        new Thread(() -> {
            synchronized (objectA){
                System.out.println(Thread.currentThread().getName()+"\t 自己持有A锁,希望获得B锁");
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectB){
                    System.out.println(Thread.currentThread().getName()+"\t 成功获得B锁");
                }
            }
        },"A").start();
        new Thread(() -> {
            synchronized (objectB){
                System.out.println(Thread.currentThread().getName()+"\t 自己持有B锁,希望获得A锁");
                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (objectA){
                    System.out.println(Thread.currentThread().getName()+"\t 成功获得A锁");
                }
            }
        },"B").start();
    }


排查死锁


idea终端

jps -l
jstack 进程变化


jdk安装目录bin/jconsole


根据包名选择进程,连接,选择线程,下边检测死锁

1673407876873.jpg


总结:


锁的到底是什么?


对象锁、类锁

公平锁和非公平锁


多个线程在抢锁竞争同一份资源的时候采用的策略是公平还是效率优先

可重入锁


同一个线程持有同一把锁,自己进入自己的内部方法以后可以自动获得锁,不会导致死锁

为什么任何一个对象都可以成为一个锁?


objectMonitor.hpp

1673407891469.jpg

1673407900887.jpg

指针指向monitor对象(也称为管程或监视器锁)的其实地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是ObjectMonitor实现的

相关文章
|
16天前
|
存储 Oracle 安全
揭秘Java并发核心:深入Hotspot源码腹地,彻底剖析Synchronized关键字的锁机制与实现奥秘!
【8月更文挑战第4天】在Java并发世界里,`Synchronized`如同导航明灯,确保多线程环境下的代码安全执行。它通过修饰方法或代码块实现独占访问。在Hotspot JVM中,`Synchronized`依靠对象监视器(Object Monitor)机制实现,利用对象头的Mark Word管理锁状态。
27 1
|
3天前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
30天前
|
负载均衡 NoSQL Java
|
1月前
|
安全 算法 Java
Java 中的并发控制:锁与线程安全
在 Java 的并发编程领域,理解并正确使用锁机制是实现线程安全的关键。本文深入探讨了 Java 中各种锁的概念、用途以及它们如何帮助开发者管理并发状态。从内置的同步关键字到显式的 Lock 接口,再到原子变量和并发集合,本文旨在为读者提供一个全面的锁和线程安全的知识框架。通过具体示例和最佳实践,我们展示了如何在多线程环境中保持数据的一致性和完整性,同时避免常见的并发问题,如死锁和竞态条件。无论你是 Java 并发编程的新手还是有经验的开发者,这篇文章都将帮助你更好地理解和应用 Java 的并发控制机制。
|
1月前
|
Java 数据安全/隐私保护
Java中的并发编程:锁与同步详解
Java中的并发编程:锁与同步详解
|
1月前
|
Java
Java中的锁机制及其应用
Java中的锁机制及其应用
|
2月前
|
设计模式 算法 Java
简单了解下Java中锁的概念和原理
Java的锁通过java代码实现,go语言的锁通过go实现,python语言的锁通过python实现。它们都实现的什么呢?这部分就是锁的定义和设计模式、算法、原理等一些理论上的东西。
20 1
|
2月前
|
存储 安全 算法
深入探索Java中的MarkWord与锁优化机制——无锁、偏向锁、自旋锁、重量级锁
深入探索Java中的MarkWord与锁优化机制——无锁、偏向锁、自旋锁、重量级锁
27 1
|
2月前
|
Java
JAVA单例模式-双重检验锁(防止反射、序列化多个)
JAVA单例模式-双重检验锁(防止反射、序列化多个)
29 1