美文网首页线程
Thread的常用方法和线程的基本状态

Thread的常用方法和线程的基本状态

作者: 朦胧蜜桃 | 来源:发表于2018-05-11 13:30 被阅读121次

    在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。

    • 创建(new)状态: 准备好了一个多线程的对象
    • 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
    • 运行(running)状态: 执行run()方法
    • 阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
    • 终止(dead)状态: 线程销毁

    当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。

    当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。

    线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

    当由于突然中断或者子任务执行完毕,线程就会被消亡。

    下面这副图描述了线程从创建到消亡之间的状态:

    image

    在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是可以的,只不过这里我想将线程的状态和Java中的方法调用联系起来,所以将waiting和time waiting两个状态分离出来。

    注:sleep和wait的区别:

    • sleepThread类的方法,waitObject类中定义的方法.
    • Thread.sleep不会导致锁行为的改变, 如果当前线程是拥有锁的, 那么Thread.sleep不会让线程释放锁.
    • Thread.sleepObject.wait都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait后, 需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间.

    上下文切换

    对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

    由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

    因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

    说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行

    虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

    线程的常用方法

    编号 方法 说明
    1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
    2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
    3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。
    4 public final void setPriority(int priority) 更改线程的优先级。
    5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
    6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
    7 public void interrupt() 中断线程。
    8 public final boolean isAlive() 测试线程是否处于活动状态。
    9 public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
    10 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
    11 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。

    image

    静态方法

    currentThread()方法

    currentThread()方法可以返回代码段正在被哪个线程调用的信息。

    public class Run1{

    public static void main(String[] args){

    System.out.println(Thread.currentThread().getName());

    }

    }

    |

    sleep()方法

    方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

    sleep方法有两个重载版本:

    sleep(``long millis) //参数为毫秒

    sleep(``long millis,``int nanoseconds) //第一参数为毫秒,第二个参数为纳秒

    sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
    但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:

    public class Test {

    private int i = 10``;

    private Object object = new Object();

    public static void main(String[] args) throws IOException {

    Test test = new Test();

    MyThread thread1 = test.``new MyThread();

    MyThread thread2 = test.``new MyThread();

    thread1.start();

    thread2.start();

    }

    class MyThread extends Thread{

    @Override

    public void run() {

    synchronized (object) {

    i++;

    System.out.println(``"i:"``+i);

    try {

    System.out.println(``"线程"``+Thread.currentThread().getName()+``"进入睡眠状态"``);

    Thread.currentThread().sleep(``10000``);

    } catch (InterruptedException e) {

    // TODO: handle exception

    }

    System.out.println(``"线程"``+Thread.currentThread().getName()+``"睡眠结束"``);

    i++;

    System.out.println(``"i:"``+i);

    }

    }

    }

    }

    |

    输出结果:

    image

    从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。

    注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。

    yield()方法

    调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

    注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
    代码:

    public class MyThread extends Thread{

    @Override

    public void run() {

    long beginTime=System.currentTimeMillis();

    int count=``0``;

    for (``int i=``0``;i<``50000000``;i++){

    count=count+(i+``1``);

    //Thread.yield();

    }

    long endTime=System.currentTimeMillis();

    System.out.println(``"用时:"``+(endTime-beginTime)+``" 毫秒!"``);

    }

    }

    public class Run {

    public static void main(String[] args) {

    MyThread t= new MyThread();

    t.start();

    }

    }

    执行结果:

    用时:``3 毫秒!

    如果将 //Thread.yield();的注释去掉,执行结果如下:

    用时:``16080 毫秒!

    对象方法

    start()方法

    start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

    run()方法

    run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

    getId()

    getId()的作用是取得线程的唯一标识
    代码:

    public class Test {

    public static void main(String[] args) {

    Thread t= Thread.currentThread();

    System.out.println(t.getName()+``" "``+t.getId());

    }

    }

    输出:

    main 1

    isAlive()方法

    方法isAlive()的功能是判断当前线程是否处于活动状态
    代码:

    public class MyThread extends Thread{

    @Override

    public void run() {

    System.out.println(``"run="``+``this``.isAlive());

    }

    }

    public class RunTest {

    public static void main(String[] args) throws InterruptedException {

    MyThread myThread=``new MyThread();

    System.out.println(``"begin =="``+myThread.isAlive());

    myThread.start();

    System.out.println(``"end =="``+myThread.isAlive());

    }

    }

    |

    程序运行结果:

    begin ==``false

    run=``true

    end ==``false

    方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
    有个需要注意的地方

    System.out.println(``"end =="``+myThread.isAlive());

    虽然上面的实例中打印的值是true,但此值是不确定的。打印true值是因为myThread线程还未执行完毕,所以输出true。如果代码改成下面这样,加了个sleep休眠:

    public static void main(String[] args) throws InterruptedException {

    MyThread myThread=``new MyThread();

    System.out.println(``"begin =="``+myThread.isAlive());

    myThread.start();

    Thread.sleep(``1000``);

    System.out.println(``"end =="``+myThread.isAlive());

    }

    则上述代码运行的结果输出为false,因为mythread对象已经在1秒之内执行完毕。

    join()方法

    在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

    public class Thread4 extends Thread{

    public Thread4(String name) {

    super``(name);

    }

    public void run() {

    for (``int i = 0``; i < 5``; i++) {

    System.out.println(getName() + " " + i);

    }

    }

    public static void main(String[] args) throws InterruptedException {

    // 启动子进程

    new Thread4(``"new thread"``).start();

    for (``int i = 0``; i < 10``; i++) {

    if (i == 5``) {

    Thread4 th = new Thread4(``"joined thread"``);

    th.start();

    th.join();

    }

    System.out.println(Thread.currentThread().getName() + " " + i);

    }

    }

    }

    相关文章

      网友评论

        本文标题:Thread的常用方法和线程的基本状态

        本文链接:https://www.haomeiwen.com/subject/vmaxdftx.html