java线程的一生

写在前面的话

学习java多线程时,你是否对java线程的一生了如指掌(从创建到终止),java一生中经历的状态(new、runnable、block、waiting、time waiting、terminated )你是否已经清晰掌握。本文将结合JDK源码,给出java中定义的线程五种状态,并解释其代表的含义;然后给出五种状态的转移图,分析每种状态转移为何可以进行,在这其间会涉及到与线程相关类(Object、Thread)中的方法(wait()、notify()、sleep()、join()、yield()等),文中会解释这些方法,并用例子进行说明。希望读完博主的本篇博文,对你理解java多线程有所帮助,喜欢博文的话,点赞关注收藏o!

1、java中定义线程的五种状态

java在Thread类中定义线程的五种状态,如下图:

public enum State {
        
        NEW,
        
        RUNNABLE,

        BLOCKED,
      
        WAITING,

        TERMINATED;
    }

下面分别解释这五种状态代表的意义:

NEW(新创建)

当使用new操作符创建一个线程,但是还没有调用线程的start()方法,如new Thread(r),这个时候线程处于NEW状态。

RUNNABLE(可运行)

一旦

调用了start()方法,线程就会处于RUNNABLE状态。处于RUNNABLE状态的线程包含了两种情况:1、获得CPU资源,正在运行;2、正在竞争CPU资源,处于可运行状态。java将上述两种情况定义合并成一种,切记不要根据RUNNABLE字面意思断章取义,认为这个状态只表示一种状态。下文为了叙述的方便性,并且为了理解的生动性,将这里RUNNABLE状态,分为runnable与running两种状态。

Blocked(被阻塞)

当一个线程试图获取一个对象锁,而这个对象锁正在被其它线程所持有,那么这个线程将会进入阻塞(Blocked)状态。当其它线程释放对象锁,并且调度器允许本线程持有锁的时候,该线程编程非阻塞状态。

WAITING(等待)

当线程等待另一个线程通知调度器一个条件时,它自己进入等待(WAITING)状态。当调用Object.wait()、Thread.join()方法,或者是等待java.util.concurrent库中的Lock或Condition时,会出现WAITING状态。这里其实还有一种叫Time Waiting(计时等待),java中把它合并在WAITING状态中了。Time Waiting状态是带有超时参数的,当调用Object.wait(time)、Thread.join(time)等方法时,会进入。

TERMINATED(终止)

当线程的run方法结束或者被其它线程终端时,会进入TERMINATED状态。

下面给出线程的状态转移图:

2、Object与Thread中关于线程状态转移的方法

2.1、Object中的wait()与notify()

2.1.1、wait()

JDK1.6(JDK1.8中与1.6中对Object类中的方法描述一致)中对wait()描述如下:

其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。

当前线程必须拥有对象监视器。该线程发布对此监视器的所有权并等待(释放锁并阻塞),直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。

此方法只应由作为此对象监视器的所有者的线程来调用。

总结:wait()方法是属于Object类的(Object类是所有类的上帝类,所有的类都继承了这个类),因此每个类(无论是自己定义的、还是JDK类库或者其他第三方类)都拥有wait()方法。使用wait方法时,一定要注意首先获取对象监视器(即首先需要获取锁),如果没有获取就进行调用,会抛出IllegalMonitorStateException异常;调用wait方法后,将会释放监视器锁,本线程阻塞,直到等待其他线程调用此对象监视器的notify或notifyAll方法时,线程将会被唤醒,与其他争夺此监视器的线程一起竞争,其本身在竞争时并没有其他优势或特权。

2.1.3、notify()

JDK1.6(JDK1.8中与1.6中对Object类中的方法描述一致)中对notify()描述如下:

唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势

2.1.3、wait()与notify()的例子

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        //设置监视器锁
        Object lock = new Object();
        //wait()方法的子线程
        Runnable runnable_wait = new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        System.out.println("开始wait方法");
                        lock.wait();
                        System.out.println("完成wait方法");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        //notify()方法的子线程
        Runnable runnable_notify = new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("开始notify方法");
                    lock.notify();
                    System.out.println("完成notify方法");

                }
            }
        };

        //先开启wait线程
        new Thread(runnable_wait).start();
        //主线程睡眠1s,确保wait线程开启成功
        Thread.sleep(1000);
        //开启notify线程
        new Thread(runnable_notify).start();
    }
}

输出为:

开始wait方法
开始notify方法
完成notify方法
完成wait方法

 程序构建了两个子线程,分别用来运行wait与notify方法。首先启动wait的子线程,并且在主线程睡眠1s后,开启notify线程,从而确保wait主线程已经运行并调用wait‘方法’。通过打印的结果可以验证:wait子线程运行后,在wait阻塞并释放锁,notify线程线程运行,从而使得wait线程完成。

3、Thread类中与线程转移相关的方法

3.1、yield()方法

yield翻译过来是“放弃、屈服”的意思,yield()这个方法表示线程主动提示调度器放弃CPU权限(调度器会参考,不一定运行),但是还是会持有已经获取的锁。具体的文档解释看JDK1.8的说明:

A hint(提示) to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilise a CPU. Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.

It is rarely appropriate to use this method(这个方法很少使用). It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions(使用yield()有助于重现由于竞争条件而产生的bug). It may also be useful when designing concurrency control constructs such as the ones in the java.util.concurrent.locks package.

 下面使用yield()重现死锁的情况:两个子线程分别需要获取lock1与lock2,只是获取的顺序不一致;线程1先获取lock1,再获取lock2;线程2先获取lock2,再获取lock1;从而重现死锁条件。

public class Test {
    //构建两个锁对象
    static final Object lock1 = new Object();
    static final Object lock2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        //线程1的工作逻辑:现获得lock1,然后调用yield()放弃掉CPU执行权限(yield()是不会放弃已经获得的锁),
        // 等下次获取CPU时间片时,获取lock2,输出“1”结束程序
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                synchronized (lock1) {
                    Thread.yield();
                    synchronized (lock2) {
                        System.out.println(1);
                    }
                }
            }
        };
        //线程2的工作逻辑:现获得lock2,然后调用yield()放弃掉CPU执行权限(yield()是不会放弃已经获得的锁),
        // 等下次获取CPU时间片时,获取lock1,输出“2”结束程序
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                synchronized (lock2) {
                    Thread.yield();
                    synchronized (lock1) {
                        System.out.println(2);
                    }
                }
            }
        };
        //开启线程
        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        
    }


}

不出现死锁时会出现打印出:1、2;使用yield()会重现死锁发生的条件,运行结果是整个程序 停止不下来:

 3.2、join()方法

join()方法重载方法还有join(long mills)、join(long mills,long nanos),调用join()的线程将会阻塞等待,拥有的锁会释放,直到等待的线程结束后,本线程才会继续运行。join()其实的内部实现是采用了Object.wait(),看源码:

//通过调用wait进行阻塞等待,调用isAlive()判断是否设定时间到期
 public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

下面给出例子:在主线程中开启一个子线程,用来计算1+....+10^9的和,主线程调用join()等待并对子线程用时进行计时。

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        //子线程,计算1+...+10^9的和
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                long result = 0;
                for (long i = 1; i <= Math.pow(10, 9); i++)
                    result += i;
                System.out.println(result);
            }
        };
        //记录开始时间
        long start = System.currentTimeMillis();
        //开启子线程
        Thread thread = new Thread(runnable);
        thread.start();
        //调用join()等待子线程运行结束
        thread.join();
        long end = System.currentTimeMillis();
        System.out.print("子线程用时:"+(end-start)+"毫秒");

    }
}

结果显示为:

500000000500000000
子线程用时:2001毫秒

 3.3、sleep()方法

sleep(long mills)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权

给出例子,for example:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread.sleep(1000);
        long end = System.currentTimeMillis();
        System.out.print("用时:"+(end-start));
    }
}

输出:

用时:1000

 4、总结

上述叙述了线程的一生,以及不同状态间的转移,同时阐述了线程间的wait()、notify()、join()、sleep()、yield()方法。这些方法在进行成多线程编程时会用到(线程之间的通信),是基础希望能够理解掌握。发现错误及得留言指出哦,需要探讨也欢迎留言~抱拳了!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值