技术总结(五)

一、多线程生命周期

一、新建状态(New)

当创建一个新的线程对象时,线程处于新建状态。此时线程仅仅是一个空的对象,系统还没有为其分配资源。

Thread thread = new Thread();

在这个阶段,线程对象已经创建出来,但还没有被启动执行。

二、就绪状态(Runnable)

当调用线程的start()方法后,线程进入就绪状态。处于就绪状态的线程已经具备了运行的条件,正在等待被操作系统调度执行。此时,线程会被放入就绪队列中,等待 CPU 时间片的分配。

thread.start();
三、运行状态(Running)

当线程被操作系统调度选中并获得 CPU 时间片时,线程进入运行状态,开始执行run()方法中的代码。在这个阶段,线程正在占用 CPU 资源并执行任务。

四、阻塞状态(Blocked)


在运行过程中,线程可能会因为某些原因进入阻塞状态。常见的阻塞情况有:

  1. 等待获取锁:当一个线程试图进入一个同步代码块,但该代码块被其他线程占用时,该线程会进入阻塞状态,等待锁的释放。

  2. 等待 IO 操作完成:当线程进行输入 / 输出操作(如读取文件、网络通信等)时,如果操作尚未完成,线程会进入阻塞状态,等待操作完成。
  3. 调用wait()方法:当一个线程在对象上调用wait()方法时,该线程会进入阻塞状态,直到另一个线程在该对象上调用notify()notifyAll()方法。
五、死亡状态(Terminated)

线程进入死亡状态有以下几种情况:

  1. 正常结束:当线程的run()方法执行完毕,线程自然结束。
  2. 异常退出:如果线程在执行过程中发生了未捕获的异常,导致线程异常终止。
  3. 强制终止:可以通过调用线程的stop()方法来强制终止线程,但这种方法不推荐使用,因为它可能会导致数据不一致和资源泄露等问题。

二、线程阻塞的原因有哪些?

一、等待同步锁

当一个线程试图进入一个被同步代码块保护的临界区,而该临界区正在被其他线程占用时,这个线程就会进入阻塞状态,等待持有锁的线程释放锁。

代码示例如下:

public class SynchronizedLockExample {
    private final Object lock = new Object();

    public void accessCriticalSection() {
        synchronized (lock) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedLockExample example = new SynchronizedLockExample();
        Thread thread1 = new Thread(example::accessCriticalSection);
        Thread thread2 = new Thread(example::accessCriticalSection);
        thread1.start();
        thread2.start();
    }
}

解决方法:确保同步代码块中的操作尽可能高效,避免长时间占用锁。同时,可以考虑使用更细粒度的锁或者优化业务逻辑以减少锁的竞争。 

二、等待 IO 操作完成

当线程进行输入 / 输出操作时,如读取文件、从网络接收数据、向数据库发送查询请求等,如果这些操作尚未完成,线程会进入阻塞状态等待操作完成。

public class IOBlockingExample {
    public static void readFile() {
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = br.readLine())!= null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(IOBlockingExample::readFile);
        thread.start();
    }
}

解决方法:对于频繁的 IO 操作,可以考虑使用异步 IO 框架,如 Java 的CompletableFuture结合异步文件读取或者使用非阻塞的 IO 库。这样可以在 IO 操作进行的同时,让线程去执行其他任务,而不是一直阻塞等待。 

三、等待条件满足

在使用线程间通信的机制时,一个线程可能会等待某个特定的条件满足。例如,在生产者 - 消费者模型中,消费者线程可能会在缓冲区为空时进入阻塞状态,等待生产者线程向缓冲区中放入数据。

这种情况通常会使用wait ()、notify () 和 notifyAll () 方法来实现线程间的等待和通知机制。当消费者线程发现缓冲区为空时,它会在对象上调用 wait () 方法进入阻塞状态;当生产者线程向缓冲区中放入数据后,会调用 notify () 或 notifyAll () 方法通知等待的消费者线程。

四、调用 Thread.sleep () 方法

在睡眠期间,线程不会占用 CPU 资源,直到睡眠时间结束,线程会自动进入就绪状态,等待被操作系统调度执行。

public class SleepExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                System.out.println("Before sleep");
                Thread.sleep(3000);
                System.out.println("After sleep");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        thread.start();
    }
}

这通常不需要特别的 “解决”,因为Thread.sleep()是一种有意让线程暂停一段时间的方式。如果需要更灵活的定时机制,可以考虑使用ScheduledExecutorService来安排任务在特定时间或延迟后执行。 

五、等待线程结束

如果被等待的线程已经结束,join () 方法会立即返回;如果被等待的线程还在运行,调用线程会被阻塞,直到被等待的线程结束。

public class JoinExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 1 done");
        });
        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 2 done");
        });

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

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("All threads completed");
    }
}

如果等待特定线程结束是必要的操作,可以使用join()方法,但要注意处理可能的中断异常。如果想要更灵活地控制线程的执行顺序,可以考虑使用线程池和Future对象来管理线程的执行和结果获取。 

三、线程阻塞会导致什么后果?

一、性能下降

当一个线程进入阻塞状态时,它会暂停执行,等待特定的条件满足才能继续。如果大量线程频繁进入阻塞状态,会导致程序整体的执行效率降低。例如在一个高并发的服务器应用中,如果处理请求的线程经常因为等待数据库查询结果而阻塞,那么服务器响应请求的速度就会变慢,可能无法及时处理大量的并发请求,从而影响系统的吞吐量。

二、资源占用

处于阻塞状态的线程仍然会占用一定的系统资源,如内存空间等。如果有很多线程长时间处于阻塞状态,会消耗大量的系统资源,可能导致系统资源紧张,甚至出现资源耗尽的情况。比如在一个资源有限的嵌入式系统中,过多的阻塞线程可能会使系统无法正常运行其他关键任务。

三、死锁风险

如果多个线程相互等待对方释放资源而进入阻塞状态,就有可能导致死锁的发生。一旦出现死锁,相关的线程将永远无法继续执行,整个程序可能会陷入停滞状态。例如,线程 A 持有资源 X 并等待资源 Y,而线程 B 持有资源 Y 并等待资源 X,这时两个线程就会陷入死锁。

四、响应延迟

在一些对实时性要求较高的应用中,线程阻塞可能会导致响应延迟增加。比如在一个实时控制系统中,如果控制线程因为等待某个传感器的数据而阻塞,可能会导致系统无法及时对变化的情况做出反应,影响控制的准确性和稳定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值