MST

星途 面试题库

面试题:Java中如何确保一个类的线程安全

请阐述在Java里,从设计角度出发,确保一个类线程安全的几种常见方式,并举例说明其中一种方式在实际代码中的应用。
12.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

确保类线程安全的常见方式

  1. 不可变对象:将类设计为不可变,一旦对象创建,其状态就不能被改变。例如 java.lang.String 类,所有的修改操作(如 concat)都会返回新的 String 对象,而不是修改原对象。
  2. 使用 synchronized 关键字
    • 修饰方法:当一个方法被 synchronized 修饰时,同一时间只有一个线程可以访问该方法。例如,下面的 Counter 类中的 increment 方法被 synchronized 修饰,保证了多线程环境下 count 变量自增操作的线程安全。
    public class Counter {
        private int count = 0;
        public synchronized void increment() {
            count++;
        }
        public int getCount() {
            return count;
        }
    }
    
    • 修饰代码块:可以使用 synchronized 关键字修饰代码块,对特定的代码片段进行同步。比如在 BankAccount 类的 transfer 方法中,通过 synchronized 块保证对 balance 变量的操作线程安全。
    public class BankAccount {
        private double balance;
        public BankAccount(double initialBalance) {
            this.balance = initialBalance;
        }
        public void transfer(BankAccount other, double amount) {
            synchronized (this) {
                synchronized (other) {
                    if (this.balance >= amount) {
                        this.balance -= amount;
                        other.balance += amount;
                    }
                }
            }
        }
        public double getBalance() {
            return balance;
        }
    }
    
  3. 使用 java.util.concurrent 包中的并发工具
    • Atomic:例如 AtomicInteger,它提供了原子性的操作方法,底层使用了 CAS(Compare - and - Swap)算法。以 AtomicCounter 类为例:
    import java.util.concurrent.atomic.AtomicInteger;
    public class AtomicCounter {
        private AtomicInteger count = new AtomicInteger(0);
        public void increment() {
            count.incrementAndGet();
        }
        public int getCount() {
            return count.get();
        }
    }
    
    • Lock 接口Lock 接口提供了比 synchronized 关键字更灵活的锁控制。如 ReentrantLock,在 LockCounter 类中使用 ReentrantLock 来保证 count 变量自增操作的线程安全。
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    public class LockCounter {
        private int count = 0;
        private Lock lock = new ReentrantLock();
        public void increment() {
            lock.lock();
            try {
                count++;
            } finally {
                lock.unlock();
            }
        }
        public int getCount() {
            return count;
        }
    }
    

实际代码应用举例(以 synchronized 修饰方法为例)

public class ThreadSafeExample {
    private static class SharedResource {
        private int data = 0;
        public synchronized void updateData(int value) {
            data = value;
        }
        public synchronized int getData() {
            return data;
        }
    }

    public static void main(String[] args) {
        SharedResource resource = new SharedResource();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                resource.updateData(i);
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                int value = resource.getData();
                System.out.println("Thread 2 read: " + value);
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,SharedResource 类的 updateDatagetData 方法都被 synchronized 修饰,确保了在多线程环境下对 data 变量的读写操作是线程安全的。main 方法中创建了两个线程,一个线程更新数据,另一个线程读取数据,通过 synchronized 方法保证了数据的一致性。