[Java并发编程-5] 多线程锁

[Java并发编程-5] 多线程锁,第1张

文章目录
    • 多线程锁的八种情况
    • 公平锁和非公平锁
    • 可重入锁
    • 死锁

多线程锁的八种情况

先举个常见的栗子:

一个手机类, 有发送短信(sendSMS)和发送邮件(sendEmail)的同步方法, 以及发送hello的异步方法(sendHello)

  1. 标准访问,先打印短信还是邮件
    ------sendSMS
    ------sendEmail

    谁在前面谁就先访问

  2. 在短信方法内停(sleep)4秒,先打印短信还是邮件

    ------sendSMS
    ------sendEmail

    虽然短信sleep了4s, 但是由于是在方法内的,sleep是别的类Thread的类方法, 和对象没有关系, 所以不会把对象锁解开, 等待4s之后还是先发短信

  3. 新增非同步的hello方法,是先发短信(sleep了4s)还是hello
    ------getHello
    ------sendSMS

    hello方法没有同步, 所以短信是否sleep都和hello没有关系

  4. 现在有两部手机,先打印短信还是邮件
    ------sendEmail
    ------sendSMS

    两部手机是两个对象, 两个线程获取的是不是两个不同的对象锁, 他们之间没有同步关系

  5. 两个静态同步方法,1部手机,先打印短信还是邮件
    ------sendSMS
    ------sendEmail

    静态方法访问不是对象锁, 而是类锁, 和具体的对象没有关系, 因此两个线程都是访问同一个类锁, 先获取锁的先执行(sleep不会释放锁)

  6. 两个静态同步方法,2部手机,先打印短信还是邮件
    ------sendSMS
    ------sendEmail

    不管几部手机, 都是同一个类, 访问的是同一个类锁

  7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
    ------sendEmail
    ------sendSMS

    一个访问的类锁, 一个访问的对象锁, 没有直接的关系

  8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
    ------sendEmail
    ------sendSMS

    一个访问的类锁, 一个访问的对象锁,和上面同理

所以说:

  • 异步方法不上锁, 和其他方法的各自执行, 互不关联

  • 不同的对象调用的是各自的对象锁

  • 普通同步方法调用的是对象锁, 静态同步方法调用的是静态锁, 两者不是一把锁

公平锁和非公平锁

先看看ReentrantLock的构造器源码:

  • ReentrantLock还有一个有参构造器, 参数fair表示是否公平锁
  • ReentrantLock默认都是非公平锁
  • synchronize关键字也是非公平锁

区别:

  • 在公平锁中, 不同的线程能较为公平地平均分享到资源线程, 阳光普照, 效率较低
  • 在非公平锁中, 某一个线程(不一定是开始的线程)会很大程度上优先得到到资源线程, 效率较高, 但容易饿死某些线程(总是抢不到资源)

还是考虑原来的卖票例子

如果是公平锁:

如果是非公平锁:

可重入锁

可重入锁,又称递归锁, 指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

也就是说,线程可以进入任何一个他已经拥有锁的所有同步代码块

synchronize和Lock都是可重入锁, 前者是隐式的, 后者是显式的

用synchronize写一段获取某一个特定对象锁的同步代码:

Object obj = new Object();
synchronize(obj){代码...}

用synchronize演示可重用锁:

public class ReentrantDemo{

    public static final Object o = new Object();

    public static void main(String[] args){
        new Thread(() -> {
            synchronized(o){
                System.out.println(Thread.currentThread().getName() + ": 外层");
                synchronized(o){
                    System.out.println(Thread.currentThread().getName() + ": 中层");
                    synchronized(o){
                        System.out.println(Thread.currentThread().getName() + ": 内层");
                    }
                }
            }
        }, "AAA").start();
    }

    // 该递归方法能够调用,说明外层进程能进入内层进程中, 也说明是可重用锁
    public synchronized void add(){
        add();
    }

}

用Lock演示可重用锁:

一个栗子: 把房子当成外层线程, 里面有两个内层线程: 厨房和卧室

public class House{

    private final ReentrantLock lock = new ReentrantLock();

    // 演示的时候调用house函数
    public void house(){
        lock.lock();
        try{
            System.out.println("进入房子");
            bedroom();
            kitchen();
            System.out.println("4s后从房子出来");
        }finally{
            lock.unlock();
        }
    }

    private void bedroom(){
        lock.lock();
        try{
            System.out.println("进入卧室");
            Thread.sleep(2000);
            System.out.println("2s秒后从卧室出来");
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }

    private void kitchen(){
        lock.lock();
        try{
            System.out.println("进入厨房");
            Thread.sleep(2000);
            System.out.println("2s秒后从厨房出来");
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}
死锁

什么是死锁:

两个或者两个以上线程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再执行下去

产生死锁原因:

  1. 系统资源不足
  2. 进程运行推进顺序不合适
  3. 资源分配不当

一个死锁的栗子:

public class DeadLockDemo{

    public static final Object a = new Object();
    public static final Object b = new Object();

    public static void main(String[] args){
        new Thread(() -> {
            synchronized(a){
                System.out.println("A线程拥有a锁, 正在试图获取b锁");
                try{
                    Thread.sleep(1000);
                    // 睡一秒钟, 否则可能B线程还没拿到b锁,就被A抢到了两个锁
                } catch(InterruptedException e){
                    e.printStackTrace();
                }
                synchronized(b){
                    System.out.println("A线程获取b锁");
                }
            }
        }, "AAA").start();

        new Thread(() -> {
            synchronized(b){
                System.out.println("B线程拥有b锁, 正在试图获取a锁");
                synchronized(a){
                    System.out.println("B线程获取a锁");
                }
            }
        }, "BBB").start();
    }
}

在正常写业务中如何判断死锁:

在终端打开当前目录(使用idea的终端十分方便)

使用命令:

jps -l

找到当前程序的端口号, 使用命令:

jstack [端口号]

如果最后显示Found 1 deadlock, 说明这是死锁

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/723438.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存