如何保证线程安全?5种常见方法详解

文摘   2024-09-06 12:59   四川  

关注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、中间件...等必考题答案详解。


需要以上架构专题&面试答案的同学,加我微信即可领取!


添加时备注:资料






mikechen的架构笔记
十余年BAT架构经验倾囊相授!
 最新文章