MST

星途 面试题库

面试题:Java多线程与进程在并发控制机制上的深度剖析

在Java应用场景中,多线程通过锁、信号量等机制实现并发控制。而进程也有其对应的并发控制手段。请对比分析Java多线程与进程在并发控制机制上的特点、适用场景以及可能出现的问题,并举例说明如何避免这些问题。
28.9万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 特点对比

多线程并发控制

  • 轻量级:线程共享进程资源,创建和销毁开销小,切换成本低。锁机制如synchronized关键字或java.util.concurrent.locks包下的锁,操作简单,能快速实现对共享资源的同步访问。信号量Semaphore可控制同时访问特定资源的线程数量。
  • 数据共享方便:同一进程内线程共享内存空间,数据交互容易,但也正因如此,需更谨慎处理数据一致性问题。

进程并发控制

  • 重量级:进程拥有独立的内存空间,创建和销毁开销大,进程间切换成本高。通过管道、消息队列、共享内存、信号量、套接字等IPC(Inter - Process Communication)机制实现进程间通信与并发控制。
  • 数据隔离性好:每个进程有独立内存,数据安全性高,一个进程崩溃一般不影响其他进程,但进程间通信相对复杂。

2. 适用场景对比

多线程适用场景

  • 计算密集型任务:如复杂的数值计算,多个线程可利用多核CPU并行计算,提高计算效率。例如,矩阵乘法的并行计算,不同线程负责矩阵不同部分的乘法运算。
  • I/O密集型任务:在等待I/O操作(如网络请求、文件读取)时,线程可释放CPU资源去执行其他任务,提高系统整体效率。如网络爬虫程序,多个线程可同时发起不同网页的请求。

进程适用场景

  • 需要高可靠性的场景:由于进程间相互独立,一个进程出现故障不会影响其他进程,适用于关键服务,如数据库服务器的不同服务模块采用进程方式部署。
  • 跨机器分布式场景:进程间通过套接字等方式可方便实现跨机器通信,用于构建分布式系统,如分布式文件系统中不同节点以进程形式运行并相互协作。

3. 可能出现的问题及避免方法

多线程

  • 死锁问题:多个线程相互等待对方释放锁,导致程序卡死。
    • 避免方法:破坏死锁的四个必要条件(互斥、占有并等待、不可剥夺、循环等待)。例如,按顺序加锁,所有线程获取锁的顺序一致;设置超时时间,线程获取锁超过一定时间则放弃。
    • 示例
class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1 acquired lock1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("Thread 1 acquired lock2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2 acquired lock2");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("Thread 2 acquired lock1");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

通过调整加锁顺序可避免死锁,如让两个线程都先获取lock1再获取lock2

  • 数据竞争问题:多个线程同时读写共享数据导致数据不一致。
    • 避免方法:使用同步机制,如synchronized关键字、Lock接口等对共享数据的访问进行同步。
    • 示例
class DataRaceExample {
    private static int counter = 0;

    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    synchronized (DataRaceExample.class) {
                        counter++;
                    }
                }
            });
            threads[i].start();
        }

        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Final counter value: " + counter);
    }
}

进程

  • 同步与通信复杂问题:进程间通信机制相对复杂,使用不当易导致数据传输错误或同步问题。
    • 避免方法:使用成熟的IPC框架,如Apache Mina等,简化进程间通信与同步逻辑。仔细设计通信协议,确保数据的准确传输和同步。
    • 示例:在使用共享内存进行进程间通信时,要通过信号量等机制确保一个进程写入数据时,其他进程不会同时读取或写入,防止数据损坏。
// 简单的共享内存和信号量示例(C语言,假设在Linux环境)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>

#define SHM_SIZE 1024

int main() {
    key_t key = ftok(".", 'a');
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    char *shared_memory = (char *)shmat(shmid, NULL, 0);
    if (shared_memory == (void *)-1) {
        perror("shmat");
        exit(1);
    }

    sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        exit(1);
    }

    // 写入数据前获取信号量
    sem_wait(sem);
    sprintf(shared_memory, "Hello from process");
    // 写入数据后释放信号量
    sem_post(sem);

    sem_close(sem);
    sem_unlink("/mysem");
    shmdt(shared_memory);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}
  • 资源消耗问题:进程创建和维护开销大,过多进程会导致系统资源耗尽。
    • 避免方法:合理规划进程数量,根据系统资源(如内存、CPU)情况动态调整进程数。例如,使用进程池技术,复用已创建的进程,减少进程创建和销毁开销。