进程与线程的基础(2)(锁 :synchronized)

前言:

加锁是解决多线程安全问题的一个重要方式,通过加锁可以让某些操作变成原子的操作,从而解决多线程安全问题,但是对某些线程进行多次加锁的时候,可能会出现死锁。当一个锁处于被加锁状态,如果有另一个线程也想要获取到这把锁的话就会产生“锁冲突/锁竞争”,后一个线程就会进入阻塞等待,直到前一个线程释放了该锁为止。如果这两个线程互相“不让对方”,那么此时就会出现死锁!!

1.加锁的基本方式

synchronized() {
   //你所想要实现的内容
}

ps:括号里需要一个用来加锁的对象,这个对象是啥不重要(任意对象),重要的是通过这个对像来区分两个线程是否在竞争同一把锁。

除了上述synchronized修饰代码块之外,还可以修饰实例方法/静态方法

//修饰实例方法,this可以作为锁对象进行加锁
public void increase1 () {
    synchronized(this) {
        count++;
    }    
}

synchronized public void increase2 () {
    count++;
}


//修饰静态方法,相当于针对类对象进行加锁
//Counter是自己创建的一个类,Counter.class就表示类对象
public static void increase3 () {
    synchronized(Counter.class) {
        count++;
    }    
}

synchronized public static void increase4 () {
    count++;
}

java里面所创建的对象,在对应的内存空间当中,除了你自己定义的一些属性,还有一些自带属性。比如说synchronized用的锁的对象就是一个,其中有表示当前对是否已经进行加锁。

2.锁的重要特性

synchronized的相关锁特性是可重入的所谓可重入就是指一个线程针对一把锁,加锁两次,不会出现死锁,满足这个要求就称可重入,否则不可重入。而不可重入就会造成死锁,所以在实际生存过程中,应该避免这个问题。

3.死锁(锁的不可重入性)

(1)一个线程,多次加锁 ==> 会造成死锁

看下面一段代码:

public class test{
    Object locker = new Object();
    Thread t = new Thread(() -> {
       synchronized (locker) {
           try {
               Thread.sleep(500);
           }catch (InterruptedException e) {
               e.printStackTrace();
           }
           synchronized (locker) {
               try {
                   Thread.sleep(500);
               }catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }
    });
}

我们可以从这段代码中看出只有一个锁对象那就是locker,第二次想要加锁成功就需要第一次加锁然后将锁给释放掉,而第一次想要释放锁,就需要执行完整个语句,要执行到语句的最后就需要给第二次加锁成功,此时代码才可以继续执行,这里由于第一次并没有释放锁,导致第二次加锁失败,此时线程进行阻塞。这就形成了死锁!!

(2)两个线程,两把锁 

如果是嵌套关系,就可能会造成死锁。

public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1正在执行");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker1) {
                    System.out.println("t2正在执行");
                }
            }
        });
        t1.start();
        t2.start();
}

运行结果: 

运行代码可以发现没有打印任何东西,因为此时就出现了死锁!!

如果是并列关系,就不会出现死锁。

public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t1正在执行");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("t2正在执行");
                }
            }
        });
        t1.start();
        t2.start();
    }

运行结果:

所以说如果有多个锁对象,且有多个线程的话,锁对象之间应该要写成并列的关系,避免写嵌套关系。

4.造成死锁的原因

(1)互斥使用(锁的基本特性):当一个线程持有一个线程,另一个线程也想获得这把锁就需要进行等待。

(2)不可抢占(锁的基本特性):当锁已经被线程1拿到之后,线程2只能等待线程1自动释放锁之后线程2才可能获得这把锁1,不能强行抢占过来

(3)请求保持:一个线程尝试获得多把锁(当某个线程拿到锁1之后,还想尝试获取锁2,获取锁2时,锁1不会释放)

(4)循环等待(环路等待):等待的依赖关系,形成环了

要解决死锁,只能解决(3)(4),(1)(2)属于锁自带的特性,破坏不了。所以只能去解决(3)(4)。

对于(3)来说,调整代码结构,避免写锁嵌套的逻辑结构

对于(4)来说,可以约定加锁顺序,就可以避免循环等待

5.关于sleep休眠问题

两个线程,两个锁对象实现嵌套结构可能会造成死锁,可能的其中原因之一就是:sleep是否进行休眠。

如果线程t1和t2没有进行休眠,则此时线程1会飞速进行加锁然后释放锁,此时线程t2也就不存在锁冲突了,可以顺利的进行加锁和释放锁。

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值