线程状态
一,概念介绍
**当我们在编写多线程程序时,了解线程状态是非常重要的,因为它能够帮助我们理解线程在执行过程中的行为,从而更好地控制和管理线程。**
下面是对线程状态概念的详细介绍:
-
新建状态(New):在这个状态下,线程对象已经被创建,但是还没有调用
start()
方法启动线程。此时,线程对象只是一个普通的Java对象,还没有被分配操作系统资源。 -
就绪状态(Runnable):当调用了线程对象的
start()
方法后,线程就进入了就绪状态。在这个状态下,线程已经被加入到线程调度器的就绪队列中,等待被分配CPU时间片来执行任务。 -
运行状态(Running):当线程获得了CPU时间片,开始执行任务时,它处于运行状态。在这个状态下,线程正在执行自己的任务代码。
-
阻塞状态(Blocked):线程进入阻塞状态通常是因为某些原因导致了线程无法继续执行。常见的阻塞原因包括等待I/O操作完成、等待获取锁、等待条件满足等。当阻塞的原因消失后,线程会重新进入就绪状态等待执行。
-
等待状态(Waiting):线程进入等待状态是因为它在某个对象上等待。例如,线程调用了
Object.wait()
方法或者Thread.join()
方法时会进入等待状态。在等待状态下,线程会释放掉它所持有的锁,直到其他线程唤醒它。 -
定时等待状态(Timed Waiting):和等待状态类似,但是在这个状态下,线程在等待一段时间或者等待某个条件满足之前会超时返回。例如,调用
Thread.sleep()
方法或者Object.wait(timeout)
方法时会使线程进入定时等待状态。 -
终止状态(Terminated):线程处于终止状态表示它已经执行完任务或者被提前中断。当线程的
run()
方法执行完毕或者调用了Thread.interrupt()
方法中断线程时,线程会进入终止状态。
理解线程状态的转换和含义可以帮助开发人员编写出更加可靠和高效的多线程程序,避免出现死锁、竞态条件等并发问题。
二,代码实现介绍
public static void main(String[] args) {
// 创建两个新线程
Thread thread1 = new Thread(new MyRunnable(), "线程1");
Thread thread2 = new Thread(new MyRunnable(), "线程2");
// 打印初始状态
printThreadState(thread1, "初始化");
printThreadState(thread2, "初始化");
// 启动线程1
thread1.start();
// 等待一段时间,确保线程1进入就绪态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
printThreadState(thread1, "启动");
// 启动线程2
thread2.start();
// 等待一段时间,确保线程2进入就绪态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
printThreadState(thread2, "启动");
// 中断线程1,使其进入阻塞态
thread1.interrupt();
// 等待一段时间,确保线程1进入阻塞态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
printThreadState(thread1, "中断");
// 中断线程2,使其进入等待态
thread2.interrupt();
// 等待一段时间,确保线程2进入等待态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
printThreadState(thread2, "中断");
}
static void printThreadState(Thread thread, String action) {
System.out.println("线程[" + thread.getName() + "]执行" + action + ",状态:" + thread.getState());
}
static class MyRunnable implements Runnable {
@Override
public void run() {
try {
// 让线程进入运行态
Thread.sleep(200);
System.out.println("线程正在执行任务");
} catch (InterruptedException e) {
// 捕获中断异常
System.out.println("线程被中断");
}
}
}
我添加了一些等待时间以确保我们可以观察到线程的就绪态。现在,我们可以在启动线程之后立即观察到它们的就绪态。希望这样更清晰明了。
三,死锁
线程死锁通常由四个必要条件造成,这些条件是:
-
互斥条件(Mutual Exclusion):至少有一个资源必须处于非共享模式,即一次只能被一个进程使用。如果一个进程在使用该资源时,其他进程无法访问或使用它,就称这种情况为互斥条件。
-
持有并等待(Hold and Wait):一个进程可以请求并持有多个资源,并且在等待其他资源时不释放当前已经持有的资源。这样就会导致其他进程无法访问被当前进程持有的资源,从而可能造成死锁。
-
不可抢占条件(No Preemption):已经分配给一个进程的资源不能被强制性地抢占,只能在进程使用完之后由进程自己释放。这意味着其他进程无法强行剥夺另一个进程所持有的资源,只能通过协商或等待来获取资源。
-
循环等待(Circular Wait):存在一个进程等待队列 {P1, P2, …, Pn},其中P1等待P2持有的资源,P2等待P3持有的资源,…,Pn等待P1持有的资源,形成一个环形等待的情况。
当这四个条件同时满足时,就可能导致线程死锁的发生。因此,为了避免死锁,需要在设计和实现并发系统时尽量避免这些条件的出现。
演示了如何创建一个可能导致线程死锁的情况
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
// 线程1尝试获取resource1,然后获取resource2
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1...");
try {
Thread.sleep(100); // 模拟执行一些操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2...");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and resource 2...");
}
}
});
// 线程2尝试获取resource2,然后获取resource1
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2...");
try {
Thread.sleep(100); // 模拟执行一些操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 1...");
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 2 and resource 1...");
}
}
});
// 启动两个线程
thread1.start();
thread2.start();
// 等待两个线程结束
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread exiting...");
}
在这个示例中,有两个线程分别尝试获取resource1和resource2,但它们的获取顺序相反,即线程1先获取resource1再获取resource2,而线程2先获取resource2再获取resource1。这种情况下,如果线程1获取了resource1,而线程2获取了resource2,然后它们都试图等待对方释放另一个资源,就可能发生死锁。
1.互斥条件(Mutual Exclusion):在代码中,资源resource1和resource2都是以synchronized关键字锁定的,这意味着同一时刻只能有一个线程访问它们,从而满足了互斥条件。
2.持有并等待(Hold and Wait) :每个线程在持有一个资源的同时,又试图获取另一个资源,例如,线程1在持有resource1时等待获取resource2,而线程2在持有resource2时等待获取resource1。因此,这种情况满足了持有并等待条件。
另外两个条件在这个简单的示例中并没有被完全满足:
3.不可抢占条件(No Preemption):示例中并没有体现这个条件,因为一旦一个线程获得了资源,其他线程不能强制性地抢占该资源,只能等待资源被释放。
4.循环等待(Circular Wait) :示例中存在循环等待,即线程1等待线程2持有的资源,而线程2又等待线程1持有的资源,形成了一个循环等待的情况。
破坏死锁条件示例代码
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
// 线程1尝试获取resource1,然后获取resource2
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1...");
try {
Thread.sleep(100); // 模拟执行一些操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2...");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and resource 2...");
}
}
});
// 线程2尝试获取resource1,然后获取resource2
Thread thread2 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1...");
try {
Thread.sleep(100); // 模拟执行一些操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 2...");
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 1 and resource 2...");
}
}
});
// 启动两个线程
thread1.start();
thread2.start();
// 等待两个线程结束
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread exiting...");
}
在这个修改后的版本中,线程1和线程2都按照相同的顺序获取资源,因此不会出现循环等待的情况,从而可以避免死锁。
以下是对不可抢占条件和持有并等待条件的示例代码:
1.不可抢占条件(No Preemption)
public class NoPreemptionExample {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1...");
try {
Thread.sleep(100); // 模拟执行一些操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2...");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and resource 2...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2...");
try {
Thread.sleep(100); // 模拟执行一些操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 1...");
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1 and resource 2..."