关于JAVA线程五种状态的理解

简介: 关于JAVA线程五种状态的理解

ed586c752007452993b3a89bdf8880ae.png


1) 新建


当用new关键字创建一个线程时,还没调用start 就是新建状态。


2) 就绪


调用了 start 方法之后,线程就进入了就绪阶段。此时,线程不会立即执行run方法,需要等待获取CPU资源。


3) 运行


当线程获得CPU时间片后,就会进入运行状态,开始执行run方法。


4) 阻塞


当遇到以下几种情况,线程会从运行状态进入到阻塞状态。


  • 调用sleep方法,使线程睡眠。
  • 调用wait方法,使线程进入等待。
  • 当线程去获取同步锁的时候,锁正在被其他线程持有。
  • 调用阻塞式IO方法时会导致线程阻塞。
  • 调用suspend方法,挂起线程,也会造成阻塞。


需要注意的是,阻塞状态只能进入就绪状态,不能直接进入运行状态。因为,从就绪状态到运行状态的切换是不受线程自己控制的,而是由线程调度器所决定。只有当线程获得了CPU时间片之后,才会进入运行状态。


5) 死亡


当run方法正常执行结束时,或者由于某种原因抛出异常都会使线程进入死亡状态。另外,直接调用stop方法也会停止线程。但是,此方法已经被弃用,不推荐使用。


线程常用方法


1)sleep


当调用 Thread.sleep(long millis) 睡眠方法时,就会使当前线程进入阻塞状态。millis参数指定了线程睡眠的时间,单位是毫秒。当时间结束之后,线程会重新进入就绪状态。


注意,如果当前线程获得了一把同步锁,则 sleep方法阻塞期间,是不会释放锁的。


2) wait、notify和notifyAll


首先,它们都是Object类中的方法。需要配合 Synchronized关键字来使用。


可以用于实现等待通知,  因为synchronized


调用线程的wait方法会使当前线程等待,直到其它线程调用此对象的notify/notifyAll方法。如果,当前对象锁有N个线程在等待,则notify方法会随机唤醒其中一个线程,而notifyAll会唤醒对象锁中所有的线程。需要注意,唤醒时,不会立马释放锁,只有当前线程执行完之后,才会把锁释放。


另外,wait方法和sleep方法不同之处,在于sleep方法不会释放锁,而wait方法会释放锁。wait、notify的使用如下:


为什么wait,notify和notifyAll跟synchronized一起使用?


Object.wait(),Object.notify(),Object.notifyAll()都是Object的方法,换句话说,就是每个类里面都有这些方法。


Object.wait():释放当前对象锁,并进入阻塞队列 Object.notify():唤醒当前对象阻塞队列里的任一线程(并不保证唤醒哪一个) Object.notifyAll():唤醒当前对象阻塞队列里的所有线程 为什么这三个方法要与synchronized一起使用呢?解释这个问题之前,我们先要了解几个知识点


每一个对象都有一个与之对应的监视器 每一个监视器里面都有一个该对象的锁和一个等待队列和一个同步队列 wait()方法的语义有两个,一是释放当前对象锁,另一个是进入阻塞队列,可以看到,这些操作都是与监视器相关的,当然要指定一个监视器才能完成这个操作了


notify()方法也是一样的,用来唤醒一个线程,你要去唤醒,首先你得知道他在哪儿,所以必须先找到该对象,也就是获取该对象的锁,当获取到该对象的锁之后,才能去该对象的对应的等待队列去唤醒一个线程。值得注意的是,只有当执行唤醒工作的线程离开同步块,即释放锁之后,被唤醒线程才能去竞争锁。


notifyAll()方法和notify()一样,只不过是唤醒等待队列中的所有线程


因wait()而导致阻塞的线程是放在阻塞队列中的,因竞争失败导致的阻塞是放在同步队列中的,notify()/notifyAll()实质上是把阻塞队列中的线程放到同步队列中去


值得提的一点是,synchronized是一个非公平的锁,如果竞争激烈的话,可能导致某些线程一直得不到执行。


public class WaitTest {
    private static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
        ListAdd listAdd = new ListAdd();
        Thread t1 = new Thread(() -> {
            synchronized (obj){
                try {
                    for (int i = 0; i < 10; i++) {
                        listAdd.add();
                        System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
                        Thread.sleep(300);
                        if(listAdd.getSize() == 5){
                            System.out.println("发出通知");
                            obj.notify();
                        }
                    }
                } catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (obj){
                try {
                    if(listAdd.getSize() != 5){
                        obj.wait();
                    }
                } catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("线程:"+Thread.currentThread().getName()+"被通知.");
            }
        });
        t2.start();
        Thread.sleep(1000);
        t1.start();
    }
}
class ListAdd {
    private static volatile List<String> list = new ArrayList<String>();
    public void add() {
        list.add("abc");
    }
    public int getSize() {
        return list.size();
    }
}


以上,就是创建一个t2线程,判断list长度是否为5,不是的话,就一直阻塞。然后,另外一个t1线程不停的向list中添加元素,当元素长度为5的时候,就去唤醒阻塞中的t2线程。


然而,我们会发现,此时的t1线程会继续往下执行。直到方法执行完毕,才会把锁释放。t1线程去唤醒t2的时候,只是让t2具有参与锁竞争的资格。只有t2真正获得了锁之后才会继续往下执行。


3) join


当线程调用另外一个线程的join方法时,当前线程就会进入阻塞状态。直到另外一个线程执行完毕,当前线程才会由阻塞状态转为就绪状态。


或许,你在面试中,会被问到,怎么才能保证t1,t2,t3线程按顺序执行呢。(因为,我们知道,正常情况下,调用start方法之后,是不能控制线程的执行顺序的)


public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MultiT("a"));
        Thread t2 = new Thread(new MultiT("b"));
        Thread t3 = new Thread(new MultiT("c"));
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        t3.start();
        t3.join();
    }
}
class MultiT implements Runnable{
    private String s;
    private int i;
    public MultiT(String s){
        this.s = s;
    }
    @Override
    public void run() {
        while(i<10){
            System.out.println(s+"===="+i++);
        }
    }
}


最终,我们会看到,线程会按照t1,t2,t3顺序执行。因为,主线程main总会等调用join方法的那个线程执行完之后,才会往下执行。


4) yield


Thread.yield 方法会使当前线程放弃CPU时间片,把执行机会让给相同或更高优先级的线程(yield英文意思就是屈服,放弃的意思嘛,可以理解为当前线程暂时屈服于别人了)。


注意,此时当前线程不会阻塞,只是进入了就绪状态,随时可以再次获得CPU时间片,从而进入运行状态。也就是说,其实yield方法,并不能保证,其它相同或更高优先级的线程一定会获得执行权,也有可能,再次被当前线程拿到执行权。


yield方法和sleep方法一样,也是不释放锁资源的。可以通过代码来验证这一点:


public class TestYield {
    public static void main(String[] args) {
        YieldThread yieldThread = new YieldThread();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(yieldThread);
            t.start();
        }
    }
}
class YieldThread implements Runnable {
    private int count = 0;
    @Override
    public synchronized void run() {
        for (int i = 0; i < 10; i++) {
            count ++;
            if(count == 1){
                Thread.yield();
                System.out.println("线程:"+Thread.currentThread().getName() + "让步");
            }
            System.out.println("线程:"+Thread.currentThread().getName() + ",count:"+count);
        }
    }
}

5539fb03ad964f418bc5c8a290ddfa1e.png


会看到,线程让步之后,并不会释放锁。因此,其它线程也没机会获得锁,只能把当前线程执行完之后,才会释放。(对于这一点,其实我是有疑问的。既然yield不释放锁,那为什么还要放弃执行权呢。就算放弃了执行权,别的线程也无法获得锁啊。)


所以,我的理解,yield一般用于不存在锁竞争的多线程环境中。如果当前线程执行的任务时间可能比较长,就可以选择用yield方法,暂时让出CPU执行权。让其它线程也有机会执行任务,而不至于让CPU资源一直消耗在当前线程。


5)suspend、resume


suspend 会使线程挂起,并且不会自动恢复,只有调用 resume 方法才能使线程进入就绪状态。注意,这两个方法由于有可能导致死锁,已经被废弃。

目录
相关文章
|
21天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
28天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
12天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
6天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
11天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
42 1
|
19天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
40 6
|
20天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
19天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
25天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
49 9
|
22天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。