java面试题-锁如何使用?有什么注意事项?
在Java中,锁是处理多线程并发访问共享资源的关键工具。本教程将详细介绍Java中常见的锁,包括synchronized
关键字、可重入锁ReentrantLock
和可重复读写锁ReentrantReadWriteLock
的用法。我们还将讨论锁的注意事项,以确保在多线程编程中避免常见的问题。
1. synchronized关键字的用法
synchronized
关键字是Java中最基本的锁机制,用于保护共享资源,避免多个线程同时访问导致数据不一致的问题。以下是synchronized
的三种用法:
1.1 修饰普通方法
public class TestsynchronizedNormalMethod {
private int count = 0;
private synchronized void add1000() {
for (int i = 0; i < 1000; i++) {
count++;
}
}
private void test() throws InterruptedException {
// 创建30个线程,每个线程对Testsynchronized对象的count属性加1000
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Thread t = new Thread(this::add1000);
t.start();
threads.add(t);
}
// 等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
// 打印count的值
System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
new TestsynchronizedNormalMethod().test();
}
}
1.2 修饰静态方法
public class TestsynchronizedStaticMethod {
private static int count = 0;
private static synchronized void add1000() {
for (int i = 0; i < 1000; i++) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
// 创建30个线程,每个线程对Testsynchronized对象的count属性加1000
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Thread t = new Thread(TestsynchronizedStaticMethod::add1000);
t.start();
threads.add(t);
}
// 等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
// 打印count的值
System.out.println(count);
}
}
1.3 锁定Java对象
public class TestsynchronizedCodeBlock {
private int count = 0;
private final Object obj = new Object();
private void add1000() {
synchronized (obj) {
for (int i = 0; i < 1000; i++) {
count++;
}
}
}
private void test() throws InterruptedException {
// 创建30个线程,每个线程对Testsynchronized对象的count属性加1000
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Thread t = new Thread(this::add1000);
t.start();
threads.add(t);
}
// 等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
// 打印count的值
System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
new TestsynchronizedCodeBlock().test();
}
}
2. ReentrantLock的使用示例
ReentrantLock
是Java并发包中提供的可重入锁,它相比synchronized
更加灵活,可以实现更复杂的线程同步控制。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private int count = 0;
private final Lock lock = new ReentrantLock();
private void add1000() {
lock.lock();
try {
for (int i = 0; i < 1000; i++) {
count++;
}
} finally {
lock.unlock();
}
}
private void test() throws
InterruptedException {
// 创建30个线程,每个线程对Testsynchronized对象的count属性加1000
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Thread t = new Thread(this::add1000);
t.start();
threads.add(t);
}
// 等待所有线程执行完毕
for (Thread t : threads) {
t.join();
}
// 打印count的值
System.out.println(count);
}
public static void main(String[] args) throws InterruptedException {
new ReentrantLockDemo().test();
}
}
3. ReentrantReadWriteLock的使用示例
ReentrantReadWriteLock
是可重复读写锁,适用于对共享资源进行频繁读操作而写操作较少的场景。
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockDemo {
private Map<String, Object> map = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public Object get(String key) {
Object value = null;
lock.readLock().lock();
try {
Thread.sleep(50L);
value = map.get(key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return value;
}
public void set(String key, Object value) {
lock.writeLock().lock();
try {
Thread.sleep(50L);
map.put(key, value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) {
ReentrantReadWriteLockDemo test = new ReentrantReadWriteLockDemo();
String key = "lock";
Random r = new Random();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName() + " read value=" + test.get(key));
}
}).start();
new Thread(() -> {
for (int j = 0; j < 10; j++) {
int value = r.nextInt(1000);
test.set(key, value);
System.out.println(Thread.currentThread().getName() + " write value=" + value);
}
}).start();
}
}
}
4. 锁的使用注意事项
在使用锁时,有一些注意事项需要牢记:
- 不要锁定基本类型的包装类,避免缓存问题。
- 确保锁定的是同一个对象,避免线程互斥问题。
synchronized
不支持尝试获取锁、锁超时和公平锁。- 在使用
ReentrantLock
时,一定要在finally
语句块中调用unlock()
方法,以避免死锁。 - 在高并发情况下,
ReentrantLock
的自旋可能会消耗大量CPU资源。 ReentrantReadWriteLock
适合读操作频繁、写操作较少的场景。
通过深入学习锁的使用和注意事项,你将能够编写更加高效和可维护的多线程程序。
正文到此结束
- 本文标签: Java 面试题
- 本文链接: https://www.jietongc.com/article/79
- 版权声明: 本文由大熊科技原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权