关注△mikechen的架构笔记△,十余年BAT架构经验倾囊相授
大家好,我是mikechen。
如何保证线程安全,以及保证线程安全的方式等在Java面试经常被问,下面我就详解常见的保证线程安全的方法@mikechen
最新mikechen原创超30万字《阿里架构师进阶专题合集》和《最全大厂面试题及答案总结》,请关注本公众号【mikechen的架构笔记】,后台回复:资料,即可领取。
1.使用同步代码块保证线程安全
可以使用 synchronized关键字来控制并发访问,保证同一时间只有一个线程可以访问同步代码块或方法。
如下所示:
public class Counter {
private int count;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
2.使用 Lock 接口保证线程安全
与 synchronized类似,Lock 接口也可以用来控制并发访问,比如:可以使用ReentrantLock 。
如下所示:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
Lock 接口提供了更多的灵活性和更细粒度的控制,可以支持多个条件变量。
备注:在使用 synchronized 和 Lock 接口时,要注意避免死锁问题。
3.使用原子变量保证线程安全
在多线程访问时,原子变量可以保证变量的原子性操作,即使有多个线程同时访问该变量,也可以保证操作的正确性。
如下所示:
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public void decrement() {
count.decrementAndGet();
}
public int getCount() {
return count.get();
}
}
这个例子使用了AtomicInteger类,它提供了原子操作,可以确保多线程下的线程安全。
4.使用线程安全的容器类保证线程安全
除此之外,Java 中提供了一些线程安全的容器类,如比如:ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList 等。
如下所示:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapThreadSafeExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 创建两个线程来操作ConcurrentHashMap
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
map.put("A", i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
map.put("B", i);
}
});
// 启动线程并等待执行完成
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(map); // 输出:{A=9999, B=9999}
}
}
使用这些容器类可以避免多线程访问容器时出现的并发问题。
5.避免共享资源保证线程安全
尽量避免多个线程访问同一个共享资源,可以使用线程本地变量(ThreadLocal)来保证线程的独立性。
下面是一个使用Java中的ThreadLocal来保证线程安全的示例代码:
public class ThreadLocalExample {
private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 0; i < 2; i++) {
executorService.submit(() -> {
String date = new ThreadLocalExample().formatDate(new Date());
System.out.println("Thread: " + Thread.currentThread().getName() + " formatted date: " + date);
});
}
executorService.shutdown();
}
private String formatDate(Date date) {
SimpleDateFormat dateFormat = dateFormatThreadLocal.get();
return dateFormat.format(date);
}
}
在这个示例中,我们创建了一个ThreadLocal对象,用于存储SimpleDateFormat对象,并为每个线程提供独立的SimpleDateFormat对象实例。
总之,保证线程安全的方式有多种,可以根据自己的情况选择合适保证线程安全的方法即可。
以上
最后送大家一个福利:
送我原创超30万字阿里架构师进阶专题合集。
以及给大家整理最全大厂Java面试题及答案详解,包含:Java、多线程、JVM、Spring、MySQL、Redis、中间件...等必考题答案详解。
需要以上架构专题&面试答案的同学,加我微信即可领取!
添加时备注:资料