线程的生命周期-线程控制

 

 

线程的五种状态

新建(New)   就绪(Ready) 运行(Running)  阻塞(Blocked)  死亡(Dead)

 新建状态:New

在Java中使用new关键字创建一个线程,新创建的线程将处于新建状态。在创建线程时主要是为线程分配内存并初始化其成员变量的值

就绪状态:Runnable

JVM完成了方法调用栈和程序计数器的创建,等待该线程的调度和运行。

运行状态:Running

就绪状态的线程在竞争到CPU的使用权并开始执行run方法的线程执行体时,会转为运行状态,处于运行状态的线程的主要任务就是执行run方法中的逻辑代码。

阻塞状态:Blocked

运行中的线程会主动或被动地放弃CPU的使用权并暂停运行,此时该线程将转为阻塞状态,直到再次进入可运行状态,才有机会再次竞争到CPU 使用权并转为运行状态。分为以下三种

  • 等待阻塞: 在运行状态的线程调用o.wait方法时,JVM会把该线程放入等待队列(Waitting Queue)中,线程转为阻塞状态。
  • 同步阻塞:在运行状态的线程尝试获取正在被其他线程占用的对象同步锁时, JVM会把该线程放入锁池(LockPool)中,此时线程转为阻塞状态。
  • 其他阻塞: 运行状态的线程在执行Thread.sleep(long ms), Thread.join()或者发出I/O请求时,JVM会把该线程转为阻塞状态。直到sleep()状态超时、Thread.join()等待线程终止或超时,或者I/O处理完毕,线程才重新转为可运行状态

线程死亡:Dead

线程在以下面三种方式结束后转为死亡状态

  • 线程正常结束run方法call方法执行完成。
  • 线程异常退出:运行中的线程抛出一个Error 或未捕获的Exception,线程异常退出。
  • 手动结束:调用线程对象的stop方法手动结束运行中的线程(该方式会瞬间释放线程占用的同步对象锁,导致锁混乱和死锁,不推荐使用)。

线程的基本方法

  • 线程相关的基本方法有wait、notify、notifyAll、sleep、join、yield 等,这些方法控制线程的运行,并影响线程的状态变化
线程等待:wait方法
  • 调用wait方法的线程会进入WAITING状态,只有等到其他线程的通知或被中断后才会返回。
  • 需要注意的是,在调用wait方法后会释放对象的锁,因此wait方法一般被用于同步方法或同步代码块
线程睡眠:sleep方法
  • 调用sleep方法会导致当前线程休眠。
  • 与wait方法不同的是,sleep方法不会释放当前占有的锁,会导致线程进入TIMED-WATING状态,而wait方法会导致当前线程进入WATING状态
线程让步:yield方法
  • 调用yield方法会使当前线程让出(释放)CPU执行时间片,与其他线程一起重新竞争CPU时间片。
  • 在一般情况下,优先级高的线程更有可能竞争到CPU时间片,但这不是绝对的,有的操作系统对线程的优先级并不敏感。
线程中断:interrupt方法
  • interrupt方法用于向线程发行一个终止通知信号,会影响该线程内部的一个中断标识位
  • 这个线程本身并不会因为调用了interrupt方法而改变状态(阻塞、终止等)
  • 状态的具体变化需要等待接收到中断标识的程序的最终处理结果来判定
  • 调用interrupt方法并不会中断一个正在运行的线程,也就是说处于Running状态的线程并不会因为被中断而终止,仅仅改变了内部维护的中断标识位而已
  • 若因为调用sleep方法而使线程处于TIMED-WATING状态,则这时调用interrupt方法会抛出InterruptedException,使线程提前结束TIMED-WATING状态
  • 许多声明抛出InterruptedException的方法如Thread.sleep(long mills),在抛出异常前都会清除中断标识位,所以在出异常后调用isInterrupted方法将会返回false
  • 中断状态是线程固有的一个标识位,可以通过此标识位安全终止线程。比如,在想终止一个线程时,可以先调用该线程的interrupt方法,然后在线程的run方法中根据该线程isInterrupted方法的返回状态值安全终止线程
线程加入:join方法
  • join方法用于等待其他线程终止,如果在当前线程中调用一个线程的join方法,则当前线程转为阻塞状态,等到另一个线程结束,当前线程再由阻塞状态转为就绪状态,等待获取CPU的使用权。
  • 在很多情况下,主线程生成并启动了子线程,需要等到子线程返回结果并收集和处理再退出,这时就要用到join方法
线程唤醒:notify方法
  • Object类有个notify方法,用于唤醒在此对象监视器上等待的一个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的
  • 我们通常调用其中一个对象的wait方法在对象的监视器上等待,直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他线程竞争。
  • 类似的方法还有notifyAll,用于唤醒在监视器上等待的所有线程。
后台守护线程:setDaemon方法
  • setDaemon方法用于定义一个守护线程,也叫作“服务线程”,该线程是后台线程,有一个特性,即为用户线程提供公共服务,在没有用户线程可服务时会自动离开。
  • 守护线程的优先级较低,用于为系统中的其他对象和线程提供服务。将一个用户线程设置为守护线程的方法是在线程对象创建之前用线程对象的setDaemon(true)来设置。
  • 在后台守护线程中定义的线程也是后台守护线程。后台守护线程是JVM 级别的,比如垃圾回收线程就是一个经典的守护线程,在我们的程序中不再有任何线程运行时,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以在回收JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态下运行,用于实时监控和管理系统中的可回收资源。
  • 守护线程是运行在后台的一种特殊线程,独立于控制终端并且周期性地执行某种任务或等待处理某些已发生的事件。也就是说,守护线程不依赖于终端,但是依赖于JVM,与JVM“同生共死”。在JVM中的所有线程都是守护线程时,JVM就可以退出了,如果还有一个或一个以上的非守护线程,则JVM不会退出

代码实例

public class ThreadDemo2 {
    public static void main(String [] args){
        Thread thread0 = new Thread(new ThreadTask());
        Thread thread1 = new Thread(new ThreadTask());

        for(int i= 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "\t\t" + i);
            if(i == 0){
                thread0.start();
                thread1.start();
                //让主线程自动放弃CPU的使用权限,让thread0和thread1就绪完了以后就能开始执行
                //也就是让主线程就绪,thread0和thread1都处于就绪状态,OS会自己决定调用哪个
                //还有可能会轮换到main线程
                Thread.yield();//使线程由运行状态变成就绪状态

                //让main线程彻底放弃CPU的使用权,就是让其阻塞,处于阻塞状态
                try {
                    Thread.sleep(1);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }

    }
    private static  class ThreadTask implements  Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
        }
    }
}

子线程需要调用start()方法才会就绪,但主线程是一进来就已是就绪了的。一般子线程执行完后就会自己自动死亡

控制线程 

线程休眠 

Thread类提供了休眠方法,可以让当前线程暂停一段时间

static void sleep(long millis)

static void sleep(long millis, int nanos)

sleep() vs yield()

  • sleep()会让线程进入阻塞状态,而yield()会让线程进入就绪状态
  • sleep()给其他线程运行的机会,不理会其他线程的优先级,yield()考虑线程的优先级,只会给优先级相同,或优先级更高的线程执行机会
  • sleep更可控,yield不可控

等待线程 join

Thread类提供了等待方法,可以让调用方等待该线程直至它死亡

void main(String[] args){
    Thread thread = ...;
    thread.start();
    thread.join(); //...
}

main是调用方,会一直等待thread方法执行结束,并死亡

示例

public class ThreadDemo3 {
    public static void  main(String[] args){
        testJoinThread();

    }
    private static  class JoinThreadTask implements Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 30; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
        }
    }

    public static void testJoinThread(){
        new Thread(new JoinThreadTask()).start();
        for(int i = 0; i < 30; i++){
            System.out.println(Thread.currentThread().getName() + "\t\t" + i);
            if(i == 10){
                Thread thread = new Thread(new JoinThreadTask());
                thread.start();
                try {
                    thread.join();//让main线程等待thread-1执行完毕后才会再执行
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

后台线程

  • 后台线程,也叫守护线程,或精灵线程
  • 它是在后台运行的,它的任务是为其他线程提供服务
  • 如果所有的前台线程都死亡,则后台线程会自动死亡
  • 线程默认是前台线程,Thread类提供如下方法来设置后台线程
void setDaemon(boolean on)
boolean isDaemon()

前台线程都死亡后,JVM会通知后台线程死亡,但从它接收指令做出响应,需要一定的时间。

示例

private static  class DamonThreadTask implements Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 100; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
        }
    }

    public static void testDemoThread(){
        Thread thread = new Thread(new DamonThreadTask());
        //注意是先设置为daemon线程然后再start
        //和之前的先start()再join()方法有所区别
        thread.setDaemon(true);
        thread.start();
        //让main线程打印
        for(int i = 0; i <10; i++){
            System.out.println(Thread.currentThread().getName() + "\t" + i);
        }
}
  • main线程默认是前台线程,因为main线程先结束,后台线程也跟着结束,后台线程打印从0-100,main线程打印从0-10,结果发现只要main打印完9,daemon线程也停止了执行(自动死亡,还有会一定的延迟)

线程优先级

  • 线程运行时拥有优先级,优先级高的线程则拥有较多的运行机会
  • 线程默认的优先级与它的父线程相同,而主线程具有普通优先级
  • Thread类提供了如下的成员,来处理线程的优先级;
static int MAX_PRIORITY;// 10
static int MIN_PRIORITY; // 1
static int NORM_PRIORITY; // 5

int getPriority()
void setPriority(int newPriority)
  • 线程的优先级需要操作系统的支持,虽然Java支持10种优先级,但是操作系统支持的优先级可能少于10种,所以最好不要通过数字设置线程的优先级,而是尽可能地采用静态变量来指定。
private static  class PriorityThreadTask implements Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 10000; i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
        }
    }

    public static void testPriorityThread(){
        Thread thread0 = new Thread(new PriorityThreadTask());
        Thread thread1 = new Thread(new PriorityThreadTask());
        //打印main线程的优先级
        System.out.println(Thread.currentThread().getPriority());
        System.out.println(thread0.getPriority());
        System.out.println(thread1.getPriority());

        thread0.setPriority(Thread.MAX_PRIORITY);
        thread0.start();

        thread1.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
    }
  • main线程和创建的新线程的优先级为5,而且通过将thread-0的优先级设置为最大,thread-1的优先级设置为最小,最后结果显示,thread-0先执行完毕。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值