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
枚举中:
- NEW :线程已创建但未启动(调用
start()
之前)。 - RUNNABLE :线程正在运行或准备运行(包括
Running
和Ready
子状态)。 - BLOCKED :线程在等待锁(例如等待进入
synchronized
块)。 - WAITING :线程在等待其他线程的通知(如调用
wait()
、join()
)。 - TIMED_WAITING :带有超时的等待(如
sleep()
、wait(timeout)
)。 - 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,因为两种同步锁不同
}
}
在这个例子中,incrementMethod
和incrementBlock
使用不同的锁(this
和lock
),导致结果不可预测。同步代码块的优势在于可以选择锁对象,避免不必要的竞争。
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)是指多个线程因相互等待对方持有的资源而无法继续执行的状态。死锁的四个必要条件是:
- 互斥条件:资源独占。
- 持有并等待:线程持有至少一个资源,同时等待其他资源。
- 不可抢占:资源只能自愿释放。
- 循环等待:线程间形成环路。
- 代码实践: 以下是一个死锁示例:
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方法可以同时是static
和synchronized
,这种情况下锁的是类的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
变量。- 并发工具类(如
Semaphore
、CountDownLatch
)。
-
代码实践:
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等工具。
分析步骤:
- 检查线程状态 :查看
RUNNABLE
、WAITING
、BLOCKED
等状态。 - 定位死锁 :查找
Deadlock detected
或线程间循环等待的锁。 - 分析堆栈:检查线程调用栈,定位问题代码。
- 资源竞争 :关注
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,可以快速定位问题并优化代码。