java面试常问,了解Java中的锁机制:从概念到实现

一、什么是锁?

在Java中,锁是用来控制多线程访问共享资源的机制。通过使用锁,可以确保在同一时刻只有一个线程能够访问共享资源,从而避免并发访问导致的数据不一致性和竞争条件问题。

二、synchronized锁

Java里面的锁主要有synchronized,reentrantLock,Lock三种。

synchronized关键字是Java语言提供的原生锁机制,可以用来修饰方法或代码块,实现对关键代码段的同步访问。当一个线程获取了某个对象的synchronized锁时,其他线程将会被阻塞,直到当前线程释放了该锁。

synchronized是Java中最基本的锁机制,可以用于实现对代码块或方法的同步。它属于独占锁、悲观锁、可重入锁、非公平锁。

  1. 独占锁:独占锁是一种只允许一个线程获取锁的锁机制,也称为排他锁。当一个线程获取了独占锁后,其他线程必须等待该线程释放锁才能获取锁。独占锁可以保证临界区代码的原子性和线程安全性。

  2. 悲观锁:悲观锁是一种假设会发生并发冲突的锁机制,它认为在临界区代码执行期间会有其他线程来竞争锁。因此,在使用悲观锁时,线程会先获取锁,然后再执行临界区代码,以确保数据的一致性和完整性。

  3. 可重入锁:可重入锁是指一个线程可以多次获取同一把锁,而不会出现死锁的情况。在可重入锁中,线程每次获取锁时,会记录获取的次数,只有当释放锁的次数和获取锁的次数相等时,才会真正释放锁。

  4. 非公平锁:非公平锁是一种不保证线程获取锁的顺序的锁机制。在非公平锁中,当有多个线程竞争同一把锁时,锁会随机分配给其中一个线程,而不考虑等待时间或优先级。相对于公平锁,非公平锁可能会导致某些线程长时间无法获取锁,但可以提高系统的吞吐量。

java 复制代码
public class SynchronizedExample {
    private int count = 0;

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

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Count: " + example.count);
    }
}

在上面的示例中,increment方法使用synchronized关键字修饰,确保了count变量的安全访问。两个线程分别调用increment方法来递增count变量,并最终输出结果为2000。

由于increment()方法使用了synchronized关键字修饰,当一个线程进入increment()方法时,会获取example对象的监视器锁,其他线程必须等待该线程释放锁后才能继续执行。这样可以保证对count变量的操作是线程安全的,避免了多线程并发访问导致的数据不一致问题。

三、ReentrantLock锁

ReentrantLock锁是Java中提供的显示锁实现,具有更灵活的锁定机制。与synchronized相比,ReentrantLock提供了更多的方法和功能,如可中断锁、尝试获取锁、超时获取锁、公平锁等。它继承了Lock接口,属于可重入锁、悲观锁、独占锁、互斥锁、同步锁。

ReentrantLock可以通过lock()方法获取锁,通过unlock()方法释放锁,也支持公平锁和非公平锁的机制。

java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

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

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Count: " + example.count);
    }
}
  1. ReentrantLockExample类定义了一个私有变量count用来存储计数值,并创建了一个ReentrantLock实例lock用于同步访问count。
  2. increment()方法是一个线程安全的方法,通过调用lock.lock()获取锁,然后对count进行自增操作,最后在finally块中释放锁。
  3. 在main方法中,创建了一个ReentrantLockExample实例example。
  4. 创建两个线程t1和t2,分别对example的increment()方法进行1000次递增操作。
  5. 启动线程t1和t2,然后使用t1.join()和t2.join()等待两个线程执行完成。
  6. 最后输出example的count值,即两个线程共同递增后的结果。

通过使用ReentrantLock,可以确保对共享资源的访问是线程安全的,避免了多个线程同时访问导致的数据竞争问题。通过lock和unlock方法来实现对count变量的安全访问。这种方式相比synchronized更为灵活,可以根据具体需求进行更多的控制。

四、Lock接口

Lock是Java中锁的通用接口,定义了锁的基本操作方法,如获取锁、释放锁、获取条件等。ReentrantLock实现了Lock接口,可以替代synchronized关键字进行同步操作。

java 复制代码
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();
        }
    }

    public static void main(String[] args) {
        LockExample example = new LockExample();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    example.increment();
                }
            }).start();
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + example.count);
    }
}

在这段代码中,我们定义了一个LockExample类,其中包含一个count变量和一个ReentrantLock对象lock。在increment()方法中,我们使用lock.lock()获取锁,对count进行自增操作,然后使用lock.unlock()释放锁。

在main方法中,我们创建了5个线程,每个线程对LockExample对象的increment()方法进行1000次调用。最后输出最终的count值,确保多个线程对count的操作是同步的。

五、Lock和syncronized的区别

1.实现方式

  • synchronized是Java语言的关键字,是在语言层面提供的原生支持,可以直接在方法或代码块上使用。
  • Lock是一个接口,位于java.util.concurrent.locks包下,提供了更加灵活的锁定机制,需要通过其实现类(如ReentrantLock)来使用。

2.获取锁的方式

  • synchronized是隐式获取锁的方式,当一个线程进入synchronized代码块或方法时,会自动获取对象的监视器锁。
  • Lock接口提供了显式获取锁和释放锁的方法,例如lock()和unlock(),需要手动控制获取和释放锁的过程。

3.可中断性

  • synchronized在获取锁时无法被中断,即线程在等待获取锁时无法被中断。
  • Lock接口提供了可以响应中断的锁获取方式,可以在等待锁的过程中响应中断。

4.性能

通常情况下,synchronized的性能会比Lock接口的实现类(如ReentrantLock)要好,因为synchronized是在JVM层面进行优化的。 但是在某些情况下,特别是在高并发环境下,使用Lock接口可能会比synchronized更加高效。

5.可重入性

  • synchronized是可重入的,同一个线程可以多次获取同一个对象的监视器锁。
  • Lock接口的实现类(如ReentrantLock)也是可重入的,同一个线程可以多次获取同一个锁。

6.可重入性灵活性

  • Lock接口提供了更多的灵活性和功能,例如设置超时时间、支持公平锁或非公平锁等,适用于复杂的线程同步需求。
  • synchronized是一种简单且方便的线程同步机制,在一般情况下使用起来更加方便。

六、最后的话

通过合理使用锁机制,可以保证多线程程序的正确性和效率。通过锁机制确保了共享资源的安全访问。在多线程编程中和在实际开发中,应根据具体情况选择合适的锁技术,以确保多线程程序的稳定性和性能。

能力一般,水平有限,本文可能存在纰漏或错误,如有问题欢迎指正,感谢你阅读这篇文章,如果你觉得写得还行的话,不要忘记点赞、评论、收藏哦!祝生活愉快!

相关推荐
YDS82920 分钟前
DeepSeek RAG&MCP + Agent智能体项目 —— 集成ELK日志管理系统和Prometheus监控系统
java·elk·ai·springboot·agent·prometheus·deepseek
骄马之死7 小时前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端
GoGeekBaird8 小时前
Anthropic技能"(Skills)的经验分享
后端
王码码20359 小时前
多台服务器怎么统一看状态?Beszel 轻量监控,搭起来不费事
运维·服务器·后端·安全·阿里云·接口·web
郑洁文9 小时前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
螺丝钉code10 小时前
JAVA项目 Claude code CLAUDE.md 到底应该怎么写
java·人工智能·claude code
Cosolar10 小时前
LlamaIndex 文档解析与分块策略深度解析
人工智能·面试·架构
指令集梦境10 小时前
Cursor + Spring Boot实战:从零写一个RESTful API
spring boot·后端·restful
摇滚侠11 小时前
Maven 入门+高深 单一架构案例 54-59
java·架构·maven·intellij-idea
VidDown11 小时前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman