大厂面试题详解:java中有哪些类型的锁

简介: 字节跳动大厂面试题详解:java中有哪些类型的锁

大厂面试题详解:java中有哪些类型的锁


Java中的锁类型及详解


在Java中,锁是用来控制对共享资源的访问的机制。它们提供了多线程环境下的同步和互斥,以确保线程安全性。Java中有多种类型的锁,包括对象锁、类锁、读写锁、自旋锁等。


1. 对象锁(Synchronized)


对象锁是Java中最基本的锁类型之一,使用关键字 synchronized 来实现。它可以用于同步对对象实例方法和代码块的访问。


示例代码:
public class SynchronizedExample {
    private int count = 0;

    // 对象实例方法使用对象锁
    public synchronized void increment() {
        count++;
    }

    // 对象实例方法也可以使用代码块来加锁
    public void decrement() {
        synchronized (this) {
            count--;
        }
    }
}


2. 类锁(Synchronized)


类锁与对象锁类似,但是作用于类的所有实例。使用 synchronized 关键字修饰静态方法或者通过 Class 对象实现。


示例代码:
public class ClassLockExample {
    private static int count = 0;

    // 静态方法使用类锁
    public static synchronized void increment() {
        count++;
    }

    // 通过Class对象实现类锁
    public void decrement() {
        synchronized (ClassLockExample.class) {
            count--;
        }
    }
}


3. 读写锁(ReentrantReadWriteLock)


读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这提高了读操作的并发性能。

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private int value = 0;
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    // 读取操作
    public void read() {
        lock.readLock().lock(); // 获取读锁
        try {
            System.out.println("Read value: " + value); // 输出当前值
        } finally {
            lock.readLock().unlock(); // 释放读锁
        }
    }

    // 写入操作
    public void write(int newValue) {
        lock.writeLock().lock(); // 获取写锁
        try {
            value = newValue; // 更新值
            System.out.println("Write value: " + value); // 输出更新后的值
        } finally {
            lock.writeLock().unlock(); // 释放写锁
        }
    }
}


在上面的代码中,我使用ReentrantReadWriteLock实现了一个简单的读写锁示例。这个示例包括一个私有变量value用于存储数据,以及一个ReentrantReadWriteLock对象lock用于管理并发访问。


  • read() 方法用于读取数据。它首先获取读锁,然后输出当前的value值,并最终释放读锁。
  • write(int newValue) 方法用于写入数据。它首先获取写锁,然后更新value的值为newValue,输出更新后的值,并最终释放写锁。


通过使用读写锁,我可以实现对共享资源的并发访问控制,提高了程序的并发性能。


4. 自旋锁(Spin Lock)


自旋锁是一种基于循环等待的锁,线程在获取锁时不会被挂起,而是不断地尝试获取锁。

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 自旋锁是一种基于循环等待的锁,线程在获取锁时不会被挂起,而是不断地尝试获取锁。
 */
public class SpinLockExample {
    private AtomicBoolean locked = new AtomicBoolean(false);

    /**
     * 获取锁的方法
     */
    public void lock() {
        // 使用自旋方式尝试获取锁
        while (!locked.compareAndSet(false, true)) {
            // 如果获取失败,继续尝试获取
            // 在高并发情况下,可能会导致线程长时间处于自旋状态,消耗CPU资源
        }
    }

    /**
     * 释放锁的方法
     */
    public void unlock() {
        // 释放锁,将锁状态设置为false
        locked.set(false);
    }
}


在上述代码中,我实现了一个简单的自旋锁(Spin Lock)示例。自旋锁使用了AtomicBoolean来表示锁的状态,false表示锁未被持有,true表示锁已被持有。


  • lock() 方法用于获取锁。它使用了自旋的方式来尝试获取锁,不断地循环检查锁的状态,直到成功获取锁。
  • unlock() 方法用于释放锁。它将锁的状态设置为false,表示锁已被释放。


自旋锁的优势在于避免了线程的上下文切换,适用于短时间内持有锁的情况。然而,自旋锁可能会导致线程长时间处于忙等待状态,消耗CPU资源,因此在实际应用中需要谨慎使用。


5. 重入锁(ReentrantLock)


重入锁是一种与synchronized相似的锁,但提供了比synchronized更多的灵活性和功能,例如可中断锁、公平性等。

import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock是Java并发包提供的可重入锁实现,允许同一个线程多次获取同一把锁。
 */
public class ReentrantLockExample {
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();

    /**
     * 对计数器进行加一操作
     */
    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++; // 对计数器进行加一操作
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}


在上述代码中,我展示了使用ReentrantLock实现的一个简单示例。这个示例包括一个私有计数器count和一个ReentrantLock对象lock。


  • increment() 方法用于对计数器进行加一操作。在方法执行过程中,首先通过lock()方法获取锁,然后对计数器进行加一操作,最后通过unlock()方法释放锁。


ReentrantLock是Java并发包提供的可重入锁实现,允许同一个线程多次获取同一把锁。相比于synchronized关键字,ReentrantLock提供了更多的锁定操作和更灵活的控制,适用于更复杂的并发场景。


Java中锁的应用场景和详细案例


对象锁的应用场景


对象锁通常用于保护对对象实例的访问,例如多个线程对同一个对象进行操作时,可以使用对象锁确保线程安全。


示例代码:
public class ObjectLockExample {
    private int count = 0;

    // 对象实例方法使用对象锁
    public synchronized void increment() {
        count++;
    }
}


类锁的应用场景


类锁作用于类的所有实例,常用于控制对静态变量的访问,或者对静态方法的调用。


示例代码:
public class ClassLockExample {
    private static int count = 0;

    // 静态方法使用类锁
    public static synchronized void increment() {
        count++;
    }
}


读写锁的应用场景


读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。适用于读操作远远多于写操作的场景。


示例代码:
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private int value = 0;
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        lock.readLock().lock();
        try {
            System.out.println("Read value: " + value);
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write(int newValue) {
        lock.writeLock().lock();
        try {
            value = newValue;
            System.out.println("Write value: " + value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}


自旋锁的应用场景


自旋锁适用于锁保护时间短、线程竞争不激烈的情况。它避免了线程挂起和恢复的开销,适用于多核CPU并发度高的场景。


示例代码:
import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLockExample {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待锁释放
        }
    }

    public void unlock() {
        locked.set(false);
    }
}


重入锁的应用场景


重入锁提供了比synchronized更多的灵活性和功能,例如可中断锁、公平性等。适用于复杂的同步需求场景。


示例代码:
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}


Java中锁的性能比较和最佳实践


锁的性能比较


在选择锁时,除了考虑功能和应用场景外,性能也是一个重要因素。不同类型的锁在性能上有所差异,因此需要根据具体情况进行选择。


  • 对象锁(Synchronized): JVM对Synchronized进行了优化,性能较高。但是,它是一种悲观锁,可能会导致线程阻塞和上下文切换。
  • 重入锁(ReentrantLock): 提供了比Synchronized更多的功能,例如可中断锁、公平性等。但是,它的性能略低于Synchronized。
  • 读写锁(ReentrantReadWriteLock): 适用于读操作远远多于写操作的场景,可以提高读操作的并发性能。
  • 自旋锁(SpinLock): 适用于锁保护时间短、线程竞争不激烈的情况,避免了线程挂起和恢复的开销。但是,如果锁保护时间过长或线程竞争激烈,会导致CPU消耗过多。
  • StampedLock: Java 8引入的新型锁,适用于读多写少的场景,性能优于ReentrantReadWriteLock。


锁的最佳实践


  • 选择合适的锁类型: 根据具体场景选择合适的锁类型,避免过度同步。
  • 精细化锁的粒度: 尽量缩小锁的范围,以减少锁的竞争,提高并发性能。
  • 避免死锁: 设计良好的锁顺序,避免出现死锁情况。
  • 合理使用锁的超时和中断功能: 在获取锁时可以设置超时时间,避免线程长时间等待,提高系统的响应性。
  • 使用局部变量和线程封闭: 尽量使用局部变量和线程封闭的方式,避免共享资源的竞争。
  • 优化并发数据结构: 使用Java并发包提供的并发数据结构,如ConcurrentHashMap、CopyOnWriteArrayList等,避免自己实现复杂的同步逻辑。


示例代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}


以上示例代码展示了如何使用重入锁(ReentrantLock),是一种性能较好且灵活的锁实现方式,适用于大多数并发场景。


Java并发编程锁的常见文体


1. 竞态条件(Race Conditions)


竞态条件是多线程环境下常见的问题,当多个线程同时访问共享资源,并且对资源的访问顺序产生依赖时,可能导致不确定的结果。


解决方案:


  • 使用锁来保护共享资源,确保同一时间只有一个线程访问。
  • 使用原子类(Atomic类)来实现原子操作,避免非线程安全操作。


2. 死锁(Deadlocks)


死锁是指两个或多个线程被无限期地阻塞,彼此等待对方释放资源,从而无法继续执行的情况。


解决方案:


  • 设计良好的锁顺序,避免出现循环等待的情况。
  • 使用tryLock()方法来避免死锁,及时释放已经获取的锁。


3. 上下文切换(Context Switching)


多线程之间的切换会带来上下文切换的开销,尤其是在多核CPU上,上下文切换可能成为性能瓶颈。


解决方案:


  • 减少锁的粒度,尽量缩小同步代码块的范围,减少锁竞争。
  • 使用无锁数据结构,减少对共享资源的争用。


4. 内存可见性(Memory Visibility)


在多线程环境下,如果一个线程对共享变量的修改对另一个线程是不可见的,可能导致意想不到的结果。


解决方案:


  • 使用volatile关键字来保证变量的可见性。
  • 使用synchronized关键字或ReentrantLock来保证线程间的内存可见性。


5. 并发集合的安全性


Java提供了许多并发安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,但是在特定场景下仍需注意安全性。


解决方案:


  • 选择合适的并发集合类,并了解其特性和限制。
  • 使用迭代器时,注意遍历过程中集合的修改操作,避免ConcurrentModificationException异常。
相关文章
|
1月前
|
安全 IDE Java
Java记录类型(Record):简化数据载体类
Java记录类型(Record):简化数据载体类
292 120
|
1月前
|
Java 测试技术
Java浮点类型详解:使用与区别
Java中的浮点类型主要包括float和double,它们在内存占用、精度范围和使用场景上有显著差异。float占用4字节,提供约6-7位有效数字;double占用8字节,提供约15-16位有效数字。float适合内存敏感或精度要求不高的场景,而double精度更高,是Java默认的浮点类型,推荐在大多数情况下使用。两者都存在精度限制,不能用于需要精确计算的金融领域。比较浮点数时应使用误差范围或BigDecimal类。科学计算和工程计算通常使用double,而金融计算应使用BigDecimal。
795 102
|
1月前
|
安全 Java 编译器
Java类型提升与类型转换详解
本文详解Java中的类型提升与类型转换机制,涵盖类型提升规则、自动类型转换(隐式转换)和强制类型转换(显式转换)的使用场景与注意事项。内容包括类型提升在表达式运算中的作用、自动转换的类型兼容性规则,以及强制转换可能引发的数据丢失和运行时错误。同时提供多个代码示例,帮助理解byte、short、char等类型在运算时的自动提升行为,以及浮点数和整型之间的转换技巧。最后总结了类型转换的最佳实践,如避免不必要的转换、使用显式转换提高可读性、金融计算中使用BigDecimal等,帮助开发者写出更安全、高效的Java代码。
128 0
|
2月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
330 0
|
3月前
|
缓存 Java 关系型数据库
2025 年最新华为 Java 面试题及答案,全方位打造面试宝典
Java面试高频考点与实践指南(150字摘要) 本文系统梳理了Java面试核心考点,包括Java基础(数据类型、面向对象特性、常用类使用)、并发编程(线程机制、锁原理、并发容器)、JVM(内存模型、GC算法、类加载机制)、Spring框架(IoC/AOP、Bean生命周期、事务管理)、数据库(MySQL引擎、事务隔离、索引优化)及分布式(CAP理论、ID生成、Redis缓存)。同时提供华为级实战代码,涵盖Spring Cloud Alibaba微服务、Sentinel限流、Seata分布式事务,以及完整的D
194 1
|
3月前
|
算法 架构师 Java
Java 开发岗及 java 架构师百度校招历年经典面试题汇总
以下是百度校招Java岗位面试题精选摘要(150字): Java开发岗重点关注集合类、并发和系统设计。HashMap线程安全可通过Collections.synchronizedMap()或ConcurrentHashMap实现,后者采用分段锁提升并发性能。负载均衡算法包括轮询、加权轮询和最少连接数,一致性哈希可均匀分布请求。Redis持久化有RDB(快照恢复快)和AOF(日志更安全)两种方式。架构师岗涉及JMM内存模型、happens-before原则和无锁数据结构(基于CAS)。
106 5
|
3月前
|
安全 Java API
2025 年 Java 校招面试常见问题及详细答案汇总
本资料涵盖Java校招常见面试题,包括Java基础、并发编程、JVM、Spring框架、分布式与微服务等核心知识点,并提供详细解析与实操代码,助力2025校招备战。
186 1
|
3月前
|
算法 Java 微服务
2025 年 Java 面试宝典社招春招秋招实操全方位攻略
2025年Java面试宝典涵盖核心技术及最新趋势,分为四大板块:1. Java基础:深入数据类型、多态等特性,结合学生信息管理等实例;2. JVM核心:解析内存模型与GC算法,附多线程转账等场景应用;3. 高并发方案:详解synchronized与线程池配置,提供Web服务器优化案例;4. Spring生态:剖析IoC/AOP原理,演示微服务架构实现。特别新增Java 17+特性实操,包括Record类、密封接口等语法糖,整合Spring Boot 3、响应式编程及云原生技术,通过订单状态机、API网关配置。
247 1
|
3月前
|
缓存 算法 NoSQL
校招 Java 面试高频常见知识点深度解析与实战案例详细分享
《2025校招Java面试核心指南》总结了Java技术栈的最新考点,涵盖基础语法、并发编程和云原生技术三大维度: 现代Java特性:重点解析Java 17密封类、Record类型及响应式Stream API,通过电商案例演示函数式数据处理 并发革命:对比传统线程池与Java 21虚拟线程,详解Reactor模式在秒杀系统中的应用及背压机制 云原生实践:提供Spring Boot容器化部署方案,分析Spring WebFlux响应式编程和Redis Cluster缓存策略。
88 0
|
3月前
|
Java API 微服务
2025 年 Java 校招面试全攻略:从面试心得看 Java 岗位求职技巧
《2025年Java校招最新技术要点与实操指南》 本文梳理了2025年Java校招的核心技术栈,并提供了可直接运行的代码实例。重点技术包括: Java 17+新特性(Record类、Sealed类等) Spring Boot 3+WebFlux响应式编程 微服务架构与Spring Cloud组件 Docker容器化部署 Redis缓存集成 OpenAI API调用 通过实际代码演示了如何应用这些技术,如Java 17的Record类简化POJO、WebFlux构建响应式API、Docker容器化部署。
138 5