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

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

如何保证线程安全,以及保证线程安全的方式等在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 中提供了一些线程安全的容器类,如比如:ConcurrentHashMapConcurrentLinkedQueueCopyOnWriteArrayList 等。

如下所示:

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,10年+大厂架构经验,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

👇阅读mikechen更多原创技术文章👇

阿里架构 |双11秒杀 |微服务 |云原生 |Docker |K8S |Kafka |分布式架构 |高并发架构

关注作者「mikechen」公众号,获取更多技术干货!

后台回复架构,即可获取《阿里架构师进阶专题全部合集》,后台回复面试即可获取《史上最全阿里Java面试题总结

评论交流
    说说你的看法