线程安全定义
在多线程环境中,多个线程在同一时刻对同一份资源进行写操作时,不会出现数据不一致,这就是线程安全。
因为多线程共享进程资源,必然会出现多线程竞争共享资源问题,如果不采取有效的措施,则会造成共享资源的混乱。
如下图所示:
线程安全特点
具体而言,线程安全保证了以下几点:
- 原子性(Atomicity):操作是不可分割的单元,要么全部执行成功,要么全部失败。
- 可见性(Visibility):一个线程对共享变量的修改对其他线程是可见的,即一个线程所做的修改能够被其他线程看到。
- 有序性(Ordering):操作的执行顺序是有序的,不会因为线程调度的不同而导致操作顺序混乱。
线程安全实现方法
synchronized
使用 synchronized 可以确保共享资源的互斥访问,从而避免了多线程并发访问导致的数据不一致性。
常见的有两种方式:
1)可以使用 synchronized
关键字来修饰一个代码块,以确保只有一个线程可以进入该代码块。
如下所示:
synchronized (lockObject) { // 这里的代码只能由一个线程执行 }
2)可以使用 synchronized 关键字来修饰方法,确保只有一个线程可以同时执行该方法。
public synchronized void synchronizedMethod() { // 方法内的代码只能由一个线程执行 }
ReentrantLock
ReentrantLock 可以用来实现线程安全,如下所示:
ReentrantLock lock = new ReentrantLock(); lock.lock(); // 获取锁 try { // 临界区代码 } finally { lock.unlock(); // 释放锁 }
使用 lock() 方法来获取锁,只有一个线程可以成功获取锁,其他线程将在获取锁之前等待。
需要特别注意的是,在使用 ReentrantLock 时,一定要在 finally 块中释放锁,以确保在异常情况下也能正常释放锁,避免死锁。
AtomicInteger
AtomicInteger 和其他 Atomic 类(如 AtomicLong、AtomicBoolean 等)提供了一种高效且线程安全的方式来执行特定类型的原子操作。
如下所示:
AtomicInteger atomicInt = new AtomicInteger(0); atomicInt.incrementAndGet(); // 自增1,并返回新值 atomicInt.decrementAndGet(); // 自减1,并返回新值
需要注意的是,AtomicInteger 主要用于对整数进行原子操作,如果需要对其他类型的数据进行原子操作,可以考虑使用相应的 Atomic 类或使用 synchronized 来实现线程安全。
使用线程安全的数据结构
使用线程安全的数据结构,如ConcurrentHashMap
、CopyOnWriteArrayList
等,它们内部实现了适当的同步机制。
ConcurrentHashMap 是一个线程安全的哈希表,它允许多个线程同时读取和修改不同的部分,而不会发生锁竞争。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("key1", 1); map.put("key2", 2); int value = map.get("key1");
CopyOnWriteArrayList 是一个线程安全的列表,它在写操作时会创建一个新的副本,因此读操作不会受到写操作的干扰。
CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); list.add(1); list.add(2); int value = list.get(0);
ConcurrentLinkedQueue 是一个线程安全的队列,它支持高效的并发添加和删除操作。
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); queue.offer("item1"); queue.offer("item2"); String item = queue.poll();
总之,线程安全是多线程编程中的关键问题,需要根据具体情况选择适当的实现方法,以确保共享资源的安全访问。