锁的相关知识

文章详细介绍了Java中的同步机制synchronized,包括修饰方法和代码块的用法,解释了锁竞争和互斥的概念。同时,讨论了死锁的产生条件,如循环等待,并提供了避免死锁的策略,强调了锁的可重入性在防止死锁中的作用。
摘要由CSDN通过智能技术生成

目录

一.加锁(synchronized)

1.修饰方法

2.修饰代码块

3.锁竞争

二.synchronized的特性

        1.互斥

2.可重入

三.死锁

1.导致死锁的情况

2.死锁的条件

 3.如何破除死锁


一.加锁(synchronized)

加锁是给对象加锁,我们要明确的是代码是针对哪个对象加锁的。

1.修饰方法

如果synchronized修饰普通方法(静态方法),则加在方法的返回类型前,如下三例:

synchronized public void function(){
        return ;
    }

public synchronized void function(){
        return ;
    }

public static synchronized void function(){
        return ;
    }

但如果加在返回类型与方法名之间则会报错。以上代码的前两例,加锁的对象是this,而后一例则是对类方法加锁


2.修饰代码块

修饰代码块,需要制定一个加锁的对象。进入代码块加锁,退出代码块解锁。

public void function(){
        synchronized (this){
            return;
        }
    }

3.锁竞争

        锁竞争是一个线程对某个对象加锁了,另一个线程需要对某个对象加锁,这个加锁操作会被阻塞,只有当对象解锁了,其他线程的加锁操作才能执行。synchronized具有加锁的能力。

1️⃣.如果两个线程对同一个对象进行加锁,就会出现锁竞争,按照先到先得的原则,一个线程会先获取到锁,另一个线程则会进入阻塞等待,直到第一个线程解锁,才能获取到锁。

2️⃣.如果两个线程对两个不同的对象加锁,则不会出现锁竞争,都可以成功获取到各自的锁。

3️⃣.如果两个线程,一个加锁,一个不加锁,则不会出现锁竞争。


二.synchronized的特性

1.互斥

互斥导致了锁竞争,当某个线程执行了某个对象的synchronized修饰的部分,即对对象加锁了,那么其他线程要执行同一个对象的synchronized部分,则会阻塞等待。

2.可重入

当遇到以下代码的时候,

public synchronized void function(){
        synchronized (this){
            return;
        }
    }

        线程调用这个方法,已经对this对象加锁了,而方法内部的代码还要对this对象加锁,此时,就涉及到了锁是否可重入的问题了。

        对于this对象,它已经被一个线程加锁了,那么第二个线程对它加锁时,它通过判断这两个线程是不是同一个线程,如果是同一个线程第二次加锁则不需要阻塞等待,可以直接获取到锁,那么这种锁就是可重入的,反之则是不可重入。

        那么如果是不可重入的,执行上面的代码,会出现第二次加锁要等第一个锁释放,但第一个锁释放的前提是代码块内执行完毕,那么出现了循环等待,则导致死锁。

三.死锁

1.导致死锁的情况

1️⃣.当一个线程对一个对象连续加锁两次,且锁是不可重入的,就会发生死锁。

 在java中,synchronized和ReentrantLock都是可重入的,但是在C++,Python,操作系统原生的锁都是不可重入锁。 

2️⃣.两个线程对两个不同对象加锁后,在尝试获取对方的锁。

如:

public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new Thread(){
            public  void run(){
                synchronized (o1){
                    try {
                        sleep(1000);//确保加上锁
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (o2){//o2已经加锁,加锁会阻塞等待t2线程
                        System.out.println("t1");
                    }

                }
            }
        };
        Thread t2 = new Thread(){
            public  void run(){
                synchronized (o2){
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (o1){//对o1加锁会阻塞等待t1线程
                        System.out.println("t2");
                    }

                }
            }
        };
        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }

 由于两个线程的加锁操作都要等待对方的加锁代码块执行完毕,这就导致了一个循环等待的情况,造成了死锁。

3️⃣.多个线程对多个对象加锁,再各自尝试获取已加锁对象的锁。

哲学家就餐问题。


2.死锁的条件

1️⃣.互斥使用,线程1拿到锁后,线程2要获取锁就必须等待。

2️⃣.不可抢占,线程1拿到锁后,必须是线程1主动释放,线程2无法强行获取。

3️⃣.请求和保持,线程1拿到锁A后,获取锁B,此时锁A仍然保持。

4️⃣.循环等待,线程1拿到锁A,线程2拿到锁B之后,在两把锁都没解锁的情况下,线程1获取锁B,线程2获取锁A。


 3.如何破除死锁

在构成死锁的4个基本条件中,前三种是锁的基本属性,第4个条件的发生与否是我们取决于我们的代码,所以如何破除死锁,我们就必须消除循环等待,在实际应用中,我们需要根据实际情况,给锁约定顺序,避免出现循环等待。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

todd1er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值