进程与线程区别 / Java线程状态 / 同步方法与代码块差异 / Monitor线程同步原理 / 死锁解析 / 多线程访问资源避免死锁


1. 进程和线程的区别

进程(Process)和线程(Thread)是操作系统中并发执行的基本单位,但它们有着本质上的区别。简单来说,进程是资源分配的最小单位,而线程是CPU调度的最小单位。

  • 定义与独立性

    • 进程是一个独立的执行实体,拥有自己的内存空间(包括代码段、数据段、堆栈等)。操作系统为每个进程分配独立的地址空间,因此进程之间是隔离的。
    • 线程是进程中的一个执行流,共享进程的内存空间和资源(如文件句柄、网络连接)。线程有自己的栈和寄存器,但没有独立的地址空间。
  • 开销

    • 进程创建和切换开销大,因为需要分配和回收内存、加载资源等。
    • 线程是轻量级的,创建和上下文切换更快,因为它们共享进程的资源。
  • 通信

    • 进程间通信(IPC)需要借助管道、消息队列、共享内存等机制。
    • 线程间通信更简单,可以直接通过共享变量实现(但需要同步机制避免竞争)。
  • 代码实践: 以下是一个简单的多线程示例,展示线程共享进程内存的特性:

java 复制代码
public class ProcessVsThread {
    private static int sharedCounter = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) sharedCounter++;
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) sharedCounter++;
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Shared Counter: " + sharedCounter); // 输出可能不是2000,因为无同步
    }
}

在这个例子中,两个线程共享了sharedCounter,但由于缺乏同步,结果可能小于预期。这展示了线程共享内存的特性,而如果是两个进程,则需要显式的IPC机制。


2. Java线程的可用状态

Java线程有6种状态,定义在Thread.State枚举中:

  1. NEW :线程已创建但未启动(调用start()之前)。
  2. RUNNABLE :线程正在运行或准备运行(包括RunningReady子状态)。
  3. BLOCKED :线程在等待锁(例如等待进入synchronized块)。
  4. WAITING :线程在等待其他线程的通知(如调用wait()join())。
  5. TIMED_WAITING :带有超时的等待(如sleep()wait(timeout))。
  6. TERMINATED:线程执行完成或异常终止。
  • 代码实践: 以下代码展示线程状态的转换:
java 复制代码
public class ThreadStateDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(2000); // 进入TIMED_WAITING
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println("新建时状态: " + thread.getState()); // NEW
        thread.start();
        System.out.println("启动后状态: " + thread.getState()); // RUNNABLE
        Thread.sleep(100);
        System.out.println("睡眠时状态: " + thread.getState()); // TIMED_WAITING
        thread.join();
        System.out.println("结束时状态: " + thread.getState()); // TERMINATED
    }
}

通过getState()可以实时查看线程状态,理解其生命周期对调试和优化至关重要。


3. Java的同步方法和同步代码块的区别

Java中synchronized关键字用于实现线程同步,分为同步方法和同步代码块,二者区别如下:

  • 作用范围

    • 同步方法锁定整个方法,适用于方法内所有代码都需要同步的情况。
    • 同步代码块只锁定代码块内的部分,粒度更细,性能可能更好。
  • 锁对象

    • 同步方法如果是实例方法,锁是this对象;如果是静态方法,锁是Class对象。
    • 同步代码块可以指定任意对象作为锁,灵活性更高。
  • 代码实践

java 复制代码
public class SyncDemo {
    private int counter = 0;
    private final Object lock = new Object();

    // 同步方法
    public synchronized void incrementMethod() {
        counter++;
    }

    // 同步代码块
    public void incrementBlock() {
        synchronized (lock) {
            counter++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncDemo demo = new SyncDemo();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.incrementMethod();
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.incrementBlock();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Counter: " + demo.counter); // 可能不是2000,因为两种同步锁不同
    }
}

在这个例子中,incrementMethodincrementBlock使用不同的锁(thislock),导致结果不可预测。同步代码块的优势在于可以选择锁对象,避免不必要的竞争。


4. 在Monitor内部,是如何做到线程同步的?

Java的synchronized关键字依赖于Monitor机制实现线程同步。Monitor是一种锁机制,每个Java对象都可以作为Monitor。

  • Monitor的工作原理

    • Monitor有三种状态:进入(entry)、拥有(owner)、退出(exit)。
    • 线程进入Monitor时,获取锁(monitorenter指令);退出时释放锁(monitorexit指令)。
    • 如果锁已被占用,其他线程进入等待队列(Entry Set),等待锁释放。
  • 底层实现

    • JVM通过对象的头信息(Mark Word)存储锁状态。
    • 使用CAS(Compare-And-Swap)操作实现无锁到轻量级锁的升级,若竞争加剧,升级为重量级锁(依赖OS的互斥锁)。
  • 代码实践: 以下是Monitor的基本使用:

java 复制代码
public class MonitorDemo {
    private final Object monitor = new Object();

    public void syncMethod() {
        synchronized (monitor) {
            System.out.println(Thread.currentThread().getName() + " 获取锁");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 释放锁");
        }
    }

    public static void main(String[] args) {
        MonitorDemo demo = new MonitorDemo();
        Thread t1 = new Thread(demo::syncMethod, "Thread-1");
        Thread t2 = new Thread(demo::syncMethod, "Thread-2");
        t1.start();
        t2.start();
    }
}

输出会显示线程依次获取和释放锁,Monitor确保了互斥性。


5. 解释一下Deadlock

死锁(Deadlock)是指多个线程因相互等待对方持有的资源而无法继续执行的状态。死锁的四个必要条件是:

  1. 互斥条件:资源独占。
  2. 持有并等待:线程持有至少一个资源,同时等待其他资源。
  3. 不可抢占:资源只能自愿释放。
  4. 循环等待:线程间形成环路。
  • 代码实践: 以下是一个死锁示例:
java 复制代码
public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("T1 持有 lock1");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lock2) {
                    System.out.println("T1 获取 lock2");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("T2 持有 lock2");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lock1) {
                    System.out.println("T2 获取 lock1");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

运行后,T1持有lock1等待lock2,T2持有lock2等待lock1,形成死锁。


6. 如何确保N个线程可以同时访问N个资源同时不导致Deadlock

避免死锁的关键是破坏死锁的四个条件之一,常用方法包括:

  • 资源排序:为资源编号,所有线程按相同顺序获取资源,破坏循环等待。

  • 超时机制:尝试获取锁时设置超时,失败则回退。

  • 避免嵌套锁:尽量减少锁的嵌套使用。

  • 代码实践: 以下是资源排序避免死锁的实现:

java 复制代码
public class NoDeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) { // 按顺序 lock1 -> lock2
                System.out.println("T1 持有 lock1");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lock2) {
                    System.out.println("T1 获取 lock2");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock1) { // 同样 lock1 -> lock2
                System.out.println("T2 持有 lock1");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lock2) {
                    System.out.println("T2 获取 lock2");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

通过统一锁的获取顺序(lock1 -> lock2),避免了循环等待,死锁被打破。


7. Java方法可以同时是static且同时为synchronized的么?

可以!Java方法可以同时是staticsynchronized,这种情况下锁的是类的Class对象,而不是实例对象。

  • 特点

    • static synchronized方法适用于类级别的同步,所有实例共享同一把锁。
    • 非静态synchronized方法锁的是实例对象(this)。
  • 代码实践

java 复制代码
public class StaticSyncDemo {
    public static synchronized void staticSyncMethod() {
        System.out.println(Thread.currentThread().getName() + " 在静态同步方法中");
        try { Thread.sleep(1000); } catch (Exception e) {}
    }

    public synchronized void instanceSyncMethod() {
        System.out.println(Thread.currentThread().getName() + " 在实例同步方法中");
        try { Thread.sleep(1000); } catch (Exception e) {}
    }

    public static void main(String[] args) {
        StaticSyncDemo demo = new StaticSyncDemo();
        Thread t1 = new Thread(StaticSyncDemo::staticSyncMethod, "T1");
        Thread t2 = new Thread(demo::instanceSyncMethod, "T2");

        t1.start();
        t2.start();
    }
}

运行后,T1和T2可以同时执行,因为staticSyncMethod锁的是Class对象,而instanceSyncMethod锁的是demo实例,二者互不干扰。


8. 如何理解Java的多线程同步

Java多线程同步的核心是确保多个线程在访问共享资源时不会产生竞争条件(Race Condition)。同步的目的是实现互斥性可见性

  • 互斥性 :通过锁(如synchronized)保证同一时刻只有一个线程访问临界区。

  • 可见性 :通过volatile或同步块确保线程间变量修改的可见性。

  • 实现方式

    • synchronized关键字。
    • Lock接口(如ReentrantLock)。
    • volatile变量。
    • 并发工具类(如SemaphoreCountDownLatch)。
  • 代码实践

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

public class MultiThreadSync {
    private int counter = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            counter++;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MultiThreadSync demo = new MultiThreadSync();
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) demo.increment();
            });
        }

        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();

        System.out.println("Counter: " + demo.counter); // 输出10000
    }
}

使用ReentrantLock实现同步,保证了counter的正确性。


9. 分析Java的wait和sleep的方法区别

wait()sleep()都会暂停线程,但区别显著:

  • 所属类

    • wait()Object类的方法。
    • sleep()Thread类的静态方法。
  • 锁行为

    • wait()会释放锁,线程进入等待状态,需通过notify()唤醒。
    • sleep()不释放锁,只是让线程休眠指定时间。
  • 使用场景

    • wait()用于线程间通信(如生产者-消费者模型)。
    • sleep()用于简单的延时。
  • 代码实践

java 复制代码
public class WaitVsSleep {
    private final Object lock = new Object();

    public void testWait() throws InterruptedException {
        synchronized (lock) {
            System.out.println("进入wait");
            lock.wait(2000); // 释放锁,等待2秒或被唤醒
            System.out.println("wait结束");
        }
    }

    public void testSleep() throws InterruptedException {
        synchronized (lock) {
            System.out.println("进入sleep");
            Thread.sleep(2000); // 不释放锁,休眠2秒
            System.out.println("sleep结束");
        }
    }

    public static void main(String[] args) {
        WaitVsSleep demo = new WaitVsSleep();
        new Thread(() -> {
            try { demo.testWait(); } catch (Exception e) {}
        }).start();
        new Thread(() -> {
            try { demo.testSleep(); } catch (Exception e) {}
        }).start();
    }
}

运行后,wait()会释放锁,允许其他线程进入,而sleep()不会。


10. 如何使用Thread Dump,你会如何分析ThreadDump

Thread Dump是JVM线程快照,用于分析线程状态、死锁等问题。获取方式包括:

  • jstack <pid>命令。
  • kill -3 <pid>(Unix)。
  • VisualVM、JConsole等工具。

分析步骤

  1. 检查线程状态 :查看RUNNABLEWAITINGBLOCKED等状态。
  2. 定位死锁 :查找Deadlock detected或线程间循环等待的锁。
  3. 分析堆栈:检查线程调用栈,定位问题代码。
  4. 资源竞争 :关注BLOCKED线程,分析锁的持有者。

代码实践与分析: 运行以下死锁代码并生成Thread Dump:

java 复制代码
public class ThreadDumpDemo {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lock2) {}
            }
        }, "T1");

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lock1) {}
            }
        }, "T2");

        t1.start();
        t2.start();
    }
}

运行后用jstack <pid>生成Thread Dump,可能输出如下:

vbnet 复制代码
"T1" #11 prio=5 os_prio=0 tid=0x... nid=0x... waiting for monitor entry [0x...]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at ThreadDumpDemo.lambda$main$0(ThreadDumpDemo.java:11)
    - waiting to lock <0x...> (a java.lang.Object)
    - locked <0x...> (a java.lang.Object)

"T2" #12 prio=5 os_prio=0 tid=0x... nid=0x... waiting for monitor entry [0x...]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at ThreadDumpDemo.lambda$main$1(ThreadDumpDemo.java:17)
    - waiting to lock <0x...> (a java.lang.Object)
    - locked <0x...> (a java.lang.Object)

分析:

  • T1持有lock1,等待lock2
  • T2持有lock2,等待lock1
  • 状态BLOCKED和锁的交叉等待表明存在死锁。

通过Thread Dump,可以快速定位问题并优化代码。


相关推荐
HelloZheQ1 小时前
Go:简洁高效,构建现代应用的利器
开发语言·后端·golang
caihuayuan51 小时前
[数据库之十四] 数据库索引之位图索引
java·大数据·spring boot·后端·课程设计
风象南2 小时前
Redis中6种缓存更新策略
redis·后端
程序员Bears3 小时前
Django进阶:用户认证、REST API与Celery异步任务全解析
后端·python·django
非晓为骁3 小时前
【Go】优化文件下载处理:从多级复制到零拷贝流式处理
开发语言·后端·性能优化·golang·零拷贝
北极象3 小时前
Golang中集合相关的库
开发语言·后端·golang
喵手3 小时前
Spring Boot 中的事务管理是如何工作的?
数据库·spring boot·后端
玄武后端技术栈5 小时前
什么是延迟队列?RabbitMQ 如何实现延迟队列?
分布式·后端·rabbitmq
液态不合群6 小时前
rust程序静态编译的两种方法总结
开发语言·后端·rust
bingbingyihao6 小时前
SpringBoot教程(vuepress版)
java·spring boot·后端