原创

java面试题-Java中的锁是什么?

在并发编程中,处理多个线程对共享变量的访问是一项重要的任务。为了确保数据的一致性,Java提供了多种锁机制。本教程将深入探讨Java中的锁,包括传统的synchronized关键字和并发工具包中引入的更丰富的Lock

1. 锁的基本概念

1.1 synchronized关键字

在JDK 1.5之前,我们主要使用synchronized关键字来处理锁。通过synchronized,我们可以保护一段代码块,确保同一时刻只有一个线程可以执行该代码块。

public class SynchronizedExample {
    private static int count = 0;

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

1.2 java.util.concurrent.locks.Lock

JDK 1.5引入了更灵活的锁机制,通过java.util.concurrent.locks.Lock接口及其实现类,我们可以更好地控制锁的行为。

1.2.1 可重入锁

可重入锁允许同一线程在外层方法获取锁的同时,进入内层方法自动获取锁。常见的可重入锁实现是ReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

    public void performTask() {
        lock.lock();
        try {
            // 一些需要保护的代码
        } finally {
            lock.unlock();
        }
    }
}

1.2.2 公平锁与非公平锁

公平锁表示多个线程按照申请锁的顺序来获取锁,而非公平锁允许线程按照一定策略获取锁,可能导致后申请的线程先获得锁。在ReentrantLock中,可以通过构造函数指定锁的公平性。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class FairnessExample {
    private Lock fairLock = new ReentrantLock(true); // 公平锁
    private Lock nonFairLock = new ReentrantLock(); // 非公平锁
}

1.2.3 独享锁与共享锁

独享锁表示一次只能被一个线程持有,而共享锁可以被多个线程同时持有。ReentrantLock是独享锁的例子,而ReentrantReadWriteLock提供了读写锁,读锁是共享锁,写锁是独享锁。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock readLock = readWriteLock.readLock(); // 共享锁
    private Lock writeLock = readWriteLock.writeLock(); // 独享锁
}

1.2.4 悲观锁与乐观锁

悲观锁一律对代码块进行加锁,例如synchronizedReentrantLock。乐观锁默认不加锁,通常采用CAS(比较与交换)算法不断尝试更新。悲观锁适合写操作较多的场景,而乐观锁适合读操作较多的场景。

1.3 锁的粒度

锁的粒度可以分为粗粒度锁和细粒度锁。粗粒度锁是对执行代码块进行整体加锁,而细粒度锁是锁住尽可能小的代码块。在java.util.concurrent.ConcurrentHashMap中的分段锁就是一种细粒度锁的实现。

2. 锁的升级机制

2.1 偏向锁、轻量级锁和重量级锁

JDK 1.5之后,引入了锁的升级机制,包括偏向锁、轻量级锁和重量级锁。偏向锁是通过synchronized加锁后,同一线程一直访问时获取的锁。当有其他线程访问时,升级为轻量级锁,其他线程通过自旋尝试获取锁。如果自旋一定次数仍未获取到锁,就升级为重量级锁,此时线程会进入阻塞状态。

public class LockUpgradeExample {
    private static int sharedResource = 0;

    public synchronized void synchronizedMethod() {
        // 偏向锁
        sharedResource++;
    }
}

2.2 自旋锁

自旋锁是一种尝试获取锁的线程不会立即阻塞,而是采用循环的方式不断尝试获取锁的机制。这样的好处是减少线程上下文切换的消耗,但也可能导致自旋占用CPU资源。

3. 总结

本教程深入介绍了Java中的锁机制,包括传统的synchronized关键字和更灵活的Lock接口。我们讨论了不同维度的锁分类,锁的粒度,以及锁的升级机制。了解这些概念对于写出高效、可靠的多线程程序至关重要。

正文到此结束
本文目录