如何保证线程安全,以及保证线程安全的方式等在Java面试经常被问,下面我就详解常见的保证线程安全的方法@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对象实例。
总之,保证线程安全的方式有多种,可以根据自己的情况选择合适保证线程安全的方法即可。
mikechen睿哥
mikechen睿哥,十余年BAT架构经验,资深技术专家,就职于阿里、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》