当前位置: 首页 > article >正文

面试小札:闪电五连鞭_5

1. 在Java程序中保证多线程的运行安全

 

- 使用synchronized关键字

- 可以修饰方法。例如,当一个方法被声明为 synchronized 时,同一时刻只有一个线程可以访问该方法。比如:

java

public class Counter {

    private int count = 0;

    public synchronized void increment() {

        count++;

    }

    public int getCount() {

        return count;

    }

}

在这个 Counter 类中, increment 方法被 synchronized 修饰。当多个线程访问 increment 方法时,Java会保证同一时刻只有一个线程能够执行该方法内的代码,从而避免了多个线程同时修改 count 变量导致的数据不一致问题。

- 也可以用于代码块。可以指定一个对象作为锁,例如:

java

public class SharedResource {

    private Object lock = new Object();

    private int data;

    public void modifyData(int newData) {

        synchronized (lock) {

            data = newData;

        }

    }

}

这里,只有获得 lock 对象锁的线程才能执行 synchronized 代码块中的 data = newData 语句,这样可以对 data 变量的访问进行控制,保证数据的一致性。

- 使用ReentrantLock类

-  ReentrantLock 是一种可重入锁。它提供了比 synchronized 关键字更灵活的锁机制。例如:

java

import java.util.concurrent.locks.ReentrantLock;

public class BankAccount {

    private double balance;

    private ReentrantLock lock = new ReentrantLock();

    public void deposit(double amount) {

        lock.lock();

        try {

            balance += amount;

        } finally {

            lock.unlock();

        }

    }

}

 

在 BankAccount 类的 deposit 方法中,首先通过 lock.lock() 获取锁,然后在 try - finally 块中执行对 balance 变量的操作。在 finally 块中通过 lock.unlock() 释放锁,这样可以确保锁一定会被释放,即使在操作过程中出现异常。

 

2. 读写锁可用于什么场景中

- 数据读取频繁、写入不频繁的场景

- 例如,在一个缓存系统中,多个线程可能会频繁地读取缓存中的数据,但写入操作(如更新缓存数据)相对较少。使用读写锁(如 ReentrantReadWriteLock )可以允许多个线程同时读取缓存数据,提高读取性能。只有当有线程需要写入数据时,才会排他性地获取写锁,此时其他线程(无论是读线程还是写线程)都需要等待。

 

3. 锁是什么?有什么用?有哪几种锁?

- 锁的定义和作用

- 锁是一种用于控制多个线程对共享资源访问的机制。在多线程环境中,多个线程可能会同时访问和修改共享资源,这可能会导致数据不一致、竞争条件等问题。锁可以确保在同一时刻只有一个(对于排它锁)或多个(对于共享锁)线程能够访问共享资源,从而保证程序的正确性和数据的一致性。

- 锁的种类

- 排它锁(Exclusive Lock)

- 也称为互斥锁。例如, synchronized 关键字在修饰方法或代码块时就是一种排它锁机制。在Java中, ReentrantLock 默认也是排它锁。当一个线程获取了排它锁后,其他线程不能再获取该锁,直到持有锁的线程释放锁。这种锁适用于对共享资源进行修改的操作,如写入数据到数据库、更新文件内容等场景。

- 共享锁(Shared Lock)

- 读写锁中的读锁就是一种共享锁。多个线程可以同时获取共享锁来读取共享资源,但是当有一个线程获取了排它锁(写锁)时,其他线程既不能获取排它锁也不能获取共享锁。这种锁适用于多个线程对共享资源进行读取操作的场景,如读取数据库中的数据、读取配置文件等。

- 可重入锁(Reentrant Lock)

- 像 ReentrantLock 和被 synchronized 修饰的方法或代码块都是可重入的。这意味着一个线程可以多次获取同一个锁。例如,在一个递归调用的方法中,如果该方法被 synchronized 修饰,线程在递归调用过程中可以多次获取该锁而不会发生死锁。

- 自旋锁(Spin Lock)

- 自旋锁是一种比较特殊的锁。当一个线程尝试获取自旋锁而该锁已经被其他线程占用时,这个线程不会立即进入阻塞状态,而是会在一个循环中不断地检查锁是否被释放,这个过程就像线程在“自旋”。自旋锁适用于锁被占用的时间很短的情况,因为它避免了线程在阻塞和唤醒过程中的开销,但如果锁被长时间占用,会浪费CPU资源。

 

4. 什么是死锁?

- 死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象。例如,线程A持有资源R1并且等待资源R2,而线程B持有资源R2并且等待资源R1,这样两个线程就会一直等待下去,无法继续执行,导致程序出现停滞状态。

 

5. 怎么防止死锁?

- 破坏死锁产生的必要条件

- 互斥条件:有些资源本身的性质决定了它们在使用时必须是互斥的,比如打印机,在打印一份文档时不能同时被多个任务使用。但对于一些可以共享的资源,如只读的数据结构,可以尽量采用共享访问的方式,避免强制互斥。

- 请求和保持条件:可以采用一次性申请所有资源的策略。例如,一个线程在开始执行任务前,就把它需要的所有资源都申请好,如果无法获取全部资源,就等待,直到所有资源都可用。这样就避免了线程在持有部分资源的情况下又去请求其他资源。

- 不可剥夺条件:可以设置资源的超时机制。如果一个线程长时间占用某个资源而没有释放,系统可以强制剥夺该资源并分配给其他等待的线程。不过这种方式需要谨慎使用,因为可能会导致数据不一致等问题。

- 循环等待条件:可以对资源进行排序。每个线程按照相同的顺序请求资源。例如,有资源A、B、C,所有线程都必须先请求A,再请求B,最后请求C。这样就可以避免循环等待的情况。

- 采用资源分配图算法进行死锁检测和恢复

- 可以定期地检查系统中的资源分配情况,构建资源分配图。如果发现存在循环等待的情况(即死锁),可以通过终止一个或多个涉及死锁的线程,或者抢占一些线程的资源来恢复系统的正常运行。不过这种方式可能会导致部分线程的工作丢失,需要根据具体的应用场景谨慎使用。


http://www.kler.cn/a/443125.html

相关文章:

  • 深度学习|表示学习|一个神经元可以干什么|02
  • FreeROTS学习 内存管理
  • 备忘录记事工具 四款好用的电脑备忘录记事本分享
  • sql server cdc漏扫数据
  • 【简博士统计学习方法】第1章:2. 统计学习方法的基本分类
  • execl条件比较两个sheet每个单元格的值
  • tryhackme-Pre Security-Defensive Security Intro(防御安全简介)
  • Parcel 常用插件:增强功能与性能的最佳选择
  • ASRPRO学习笔记二之语音控制LED亮灭
  • 鸿蒙操作系统简介
  • git 怎么删除一个远程分支
  • 代理IP与生成式AI:携手共创未来
  • 操作系统里的算法
  • k8s存储卷和动态创建pv
  • GaussDB 企业版轻量化部署探索(二)
  • 准备写一个内网穿透的工具
  • Redis--高并发分布式结构
  • Blazor(.razor)+VUE+elementUI适合一起用吗
  • ArcGIS地理空间平台manager存在任意文件读取漏洞
  • 开源流程引擎技术
  • SSM餐厅点餐系统--02635(免费领源码)可做计算机毕业设计JAVA、PHP、爬虫、APP、小程序、C#、C++、python、数据可视化、大数据、全套文案
  • 【OceanBase 诊断调优】—— OceanBase 数据库网络速率配置方案
  • 一个初始化bitmap的小算法
  • flask_sqlalchemy event监听查询事件
  • 【排序算法】——选择排序
  • 如何设置代理服务器爬取商品信息?