MST

星途 面试题库

面试题:并发与同步之复杂场景下的同步机制优化

假设你正在开发一个多线程的文件处理系统,多个线程会同时读写不同的文件,但有些文件存在依赖关系,例如文件A的写入需等待文件B读取完成。请设计一套同步机制来确保文件读写操作的正确性和高效性,并说明如何通过优化该同步机制来提升系统整体效率。
21.6万 热度难度
操作系统并发与同步

知识考点

AI 面试

面试题答案

一键面试

1. 设计同步机制确保文件读写操作的正确性和高效性

  • 使用ConditionLock:在Java中,可以使用java.util.concurrent.locks.Conditionjava.util.concurrent.locks.Lock来实现线程间的精确同步。
    • 为每个文件创建一个Lock对象和对应的Condition对象。例如:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class FileHandler {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    // 文件操作相关代码
}
- 对于有依赖关系的文件,比如文件A的写入需等待文件B读取完成:
class FileBHandler extends FileHandler {
    public void readFileB() {
        lock.lock();
        try {
            // 执行文件B的读取操作
            System.out.println("Reading file B");
            // 读取完成后,通知依赖它的线程
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

class FileAHandler extends FileHandler {
    public void writeFileA() {
        lock.lock();
        try {
            // 等待文件B读取完成
            condition.await();
            // 执行文件A的写入操作
            System.out.println("Writing file A");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }
}
  • 使用CountDownLatch:在某些情况下,如果依赖关系是多个文件操作完成后触发另一个操作,可以使用CountDownLatch。例如,文件A的写入依赖文件B和文件C的读取完成:
import java.util.concurrent.CountDownLatch;

class FileCHandler {
    private final CountDownLatch latch;
    public FileCHandler(CountDownLatch latch) {
        this.latch = latch;
    }
    public void readFileC() {
        try {
            // 执行文件C的读取操作
            System.out.println("Reading file C");
        } finally {
            latch.countDown();
        }
    }
}

class FileAHandler2 {
    private final CountDownLatch latch;
    public FileAHandler2(CountDownLatch latch) {
        this.latch = latch;
    }
    public void writeFileA() {
        try {
            latch.await();
            // 执行文件A的写入操作
            System.out.println("Writing file A");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(2);
        FileCHandler fileCHandler = new FileCHandler(latch);
        FileBHandler fileBHandler = new FileBHandler(latch);
        FileAHandler2 fileAHandler2 = new FileAHandler2(latch);

        Thread threadB = new Thread(fileBHandler::readFileB);
        Thread threadC = new Thread(fileCHandler::readFileC);
        Thread threadA = new Thread(fileAHandler2::writeFileA);

        threadB.start();
        threadC.start();
        threadA.start();
    }
}

2. 优化同步机制提升系统整体效率

  • 减少锁的粒度:避免对整个文件处理系统使用一把大锁,而是为每个文件或每个有依赖关系的文件组使用单独的锁。这样可以让没有依赖关系的文件读写操作并行执行。例如,将文件按类别分组,为每组文件设置独立的锁。
  • 使用读写锁:如果大部分操作是读取文件,可以使用读写锁(ReadWriteLock)。读操作可以并发执行,只有写操作时需要独占锁。在Java中,ReentrantReadWriteLock可以实现这个功能。例如:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class FileReader {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    public void readFile() {
        lock.readLock().lock();
        try {
            // 执行文件读取操作
            System.out.println("Reading file");
        } finally {
            lock.readLock().unlock();
        }
    }
}

class FileWriter {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    public void writeFile() {
        lock.writeLock().lock();
        try {
            // 执行文件写入操作
            System.out.println("Writing file");
        } finally {
            lock.writeLock().unlock();
        }
    }
}
  • 使用线程池:合理使用线程池来管理线程,避免频繁创建和销毁线程带来的开销。可以根据系统资源和文件处理任务的特点,调整线程池的大小。例如,使用ThreadPoolExecutor创建线程池:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

ExecutorService executorService = Executors.newFixedThreadPool(10);
// 提交文件处理任务到线程池
executorService.submit(() -> {
    // 文件处理代码
});
  • 预读和缓存:对于频繁读取的文件,可以采用预读机制,提前将文件内容读入缓存。在有依赖关系的文件读取时,如果缓存中有可用数据,直接从缓存读取,减少磁盘I/O操作。例如,使用Guava库的Cache来实现简单的文件内容缓存:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

class FileCache {
    private static final Cache<String, String> fileCache = CacheBuilder.newBuilder()
          .maximumSize(100)
          .build();
    public static String getFileContent(String filePath) {
        return fileCache.getIfPresent(filePath);
    }
    public static void putFileContent(String filePath, String content) {
        fileCache.put(filePath, content);
    }
}