IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> [Java并发编程-5] 多线程锁 -> 正文阅读

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

多线程锁的八种情况

先举个常见的栗子:

一个手机类, 有发送短信(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的构造器源码:

img

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

区别:

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

还是考虑原来的卖票例子

如果是公平锁:

img

如果是非公平锁:

img

可重入锁

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

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

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

img

用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();
        }
    }
}

死锁

什么是死锁:

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

img

产生死锁原因:

  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

img

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

jstack [端口号]

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

img

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-24 09:15:04  更:2022-04-24 09:17:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:50:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码