面试题答案
一键面试代码实现
以下是使用Java语言实现多线程通过Lambda表达式创建并共享资源的代码示例,使用synchronized
关键字确保资源线程安全:
class SharedResource {
private int value;
public SharedResource(int initialValue) {
this.value = initialValue;
}
public synchronized void increment() {
value++;
}
public synchronized int getValue() {
return value;
}
}
public class ThreadWithLambda {
public static void main(String[] args) {
SharedResource sharedResource = new SharedResource(0);
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
sharedResource.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
sharedResource.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final value: " + sharedResource.getValue());
}
}
选择同步机制的原因
选择synchronized
关键字是因为它是Java中最基本的同步机制。它可以修饰方法或代码块,确保在同一时间只有一个线程能够访问被同步的代码,从而保证共享资源的线程安全。对于简单的多线程资源共享场景,synchronized
使用方便,并且能够满足基本的线程安全需求。
Lambda表达式在多线程资源共享场景中的潜在风险与应对策略
- 潜在风险:
- 数据竞争:Lambda表达式本身并不会自动处理线程同步问题。如果多个线程在Lambda表达式中同时访问和修改共享资源,就会发生数据竞争,导致结果不可预测。例如在上述代码中,如果没有
synchronized
关键字,两个线程同时调用increment
方法时,就可能出现值覆盖的情况,导致最终结果不是预期的2000。 - 闭包问题:Lambda表达式会捕获外部变量。如果在多线程环境下,被捕获的变量状态不一致,也会引发问题。例如捕获了一个可变对象,不同线程对该对象的修改可能导致程序逻辑错误。
- 数据竞争:Lambda表达式本身并不会自动处理线程同步问题。如果多个线程在Lambda表达式中同时访问和修改共享资源,就会发生数据竞争,导致结果不可预测。例如在上述代码中,如果没有
- 应对策略:
- 使用同步机制:如上述代码中使用
synchronized
关键字确保对共享资源的访问是线程安全的。也可以使用java.util.concurrent
包中的锁机制,如ReentrantLock
,它提供了更灵活的锁控制。 - 不可变对象:尽量使用不可变对象。如果Lambda表达式捕获的是不可变对象,就不用担心对象状态被修改带来的问题。例如使用
String
而不是StringBuilder
,因为String
是不可变的。 - 线程局部变量:使用
ThreadLocal
类。它为每个线程提供了独立的变量副本,避免了线程间的数据竞争。例如,如果共享资源在线程间不需要共享状态,可以使用ThreadLocal
来存储每个线程自己的资源副本。
- 使用同步机制:如上述代码中使用