36-多线程--wait()和sleep()的区别+停止线程+interrupt()方法+守护线程setDaemon()+join()方法+线程组ThreadGroup+创建线程的两种便捷方式+面试题

一、wait()和sleep()的区别

1、相同点:wait()和sleep()都是用于将线程冻结的方法

2、wait()和sleep()的区别

(1)wait():可以指定时间,也可以不指定

         sleep():必须指定时间

(2)在同步中,对于CPU的执行权和锁的处理不同

         wait():释放执行权,释放锁(如果不释放锁,其他线程不能进入,就不能用notify()将其唤醒)

         sleep():释放执行权,不释放锁(能自己醒,不需要被叫)

注:wait()和sleep()都可以让线程处于冻结状态。冻结状态就是释放CPU的执行权和执行资格

(3)wait():Object中的方法

         sleep():Thread中的方法

3、Object类中

(1)public final void wait(long timeout) throws InterruptedException:在其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量前,导致当前线程等待。当前线程必须拥有此对象监视器

         参数 timeout:要等待的最长时间。意味着自己可以醒

4、面试题

问题:synchronized(this){ wait(); },在wait()处有t0、t1、t2三个线程,之后,这三个线程被notifyAll()同时唤醒。问:在同步中,某一时刻只能有一个线程在执行,现在有3个线程活着,怎么办?

分析:t0、t1、t2被唤醒后,都处于具备着CPU的执行资格,等待CPU执行权的状态。如果CPU的执行权切换到t0上,t0也不一定能执行。因为t0、t1、t2都在同步中,在同步中想要执行,必须持有锁。只有t0获取到CPU的执行权,并获取到锁,才可以执行

即 同步中活着的线程不止一个,但只有一个线程在执行。谁拿到锁,并获取到CPU的执行权,谁执行

二、停止线程

1、停止线程的方法

(1)stop()方法 -- 已过时(使用此方法会造成:程序戛然而止,不知道完成了什么,也不知道哪些工作还没有做,同时无法完成清理工作)

(2)run()方法结束(线程的任务结束,线程就自动结束了)-- 使用退出标志

2、怎样控制线程的任务结束?

      任务中都会有循环结构(要让线程去重复做很多事情),只要控制住循环,就可以结束任务,线程也就结束了

      控制循环通常用定义标记来完成(标记即为条件),最常用的标记就是boolean值

注:开启多线程的目的是因为有些代码需要运行很多次,影响了其他代码的执行。想让这两部分代码同时运行,就要开启多线程,在run()方法中写循环

class StopThread implements Runnable {
    //标记
    private boolean flag = true;

    /**
     * 对外提供一个能改变标记的方式
     */
    public void setFlag() {
        flag = false;
    }

    @Override
    public void run() {
        //定义条件标记。有标记控制,就可以让while停下
        while (flag) {
            System.out.println(Thread.currentThread().getName() + "......");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        StopThread st = new StopThread();

        Thread t0 = new Thread(st);
        Thread t1 = new Thread(st);

        t0.start();
        t1.start();

        int num = 1;
        for (; ; ) {
            if (++num == 50) {
                //将线程标记置为false
                st.setFlag();
                break;
            }
            System.out.println("main..." + num);
        }
        System.out.println("over");
    }
}

三、interrupt()方法

1、定义标记不一定能让线程停止

class StopThread implements Runnable {
    //标记
    private boolean flag = true;

    /**
     * 对外提供一个能改变标记的方式
     */
    public void setFlag() {
        flag = false;
    }

    @Override
    public synchronized void run() {
        //定义条件标记
        //此种情况定义标记没有用,因为t0、t1没有读到标记
        while (flag) {
            try {
                wait(); //t0、t1全在此等待
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "..." + e);
            }
            System.out.println(Thread.currentThread().getName() + "......");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        StopThread st = new StopThread();

        Thread t0 = new Thread(st);
        Thread t1 = new Thread(st);

        t0.start();
        t1.start();

        int num = 1;
        for (; ; ) {
            if (++num == 50) {
                //将线程标记置为false
                st.setFlag();
                break;
            }
            System.out.println("main..." + num);
        }
        System.out.println("over");
    }
}

现象:主线程结束,但t0、t1线程全被wait()。t0、t1没有结束,程序没停下来

问题:如果线程处于冻结状态,无法读取标记,如何停止线程?

解决办法:可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。但是强制动作会发生中断异常InterruptedException,需要处理

class StopThread implements Runnable {
    //标记
    private boolean flag = true;

    /**
     * 对外提供一个能改变标记的方式
     */
    public void setFlag() {
        flag = false;
    }

    @Override
    public synchronized void run() {
        //定义条件标记
        while (flag) {
            try {
                wait(); //t0、t1全在此等待
            } catch (InterruptedException e) {
                //强制动作会发生中断异常InterruptedException,需要处理
                System.out.println(Thread.currentThread().getName() + "..." + e);
                //将线程标记置为false
                setFlag();
            }
            System.out.println(Thread.currentThread().getName() + "......");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        StopThread st = new StopThread();

        Thread t0 = new Thread(st);
        Thread t1 = new Thread(st);

        t0.start();
        t1.start();

        int num = 1;
        for (; ; ) {
            if (++num == 50) {
                //可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格
                //interrupt()将当前冻结状态清除,属于强制唤醒,会发生中断异常
                t0.interrupt();
                t1.interrupt();
                break;
            }
            System.out.println("main..." + num);
        }
        System.out.println("over");
    }
}

2、interrupt()方法

(1)interrupt()方法在Thread类中

(2)public void interrupt():中断线程。如果线程在调用Object类的wait()、wait(long)或wait(long, int)方法,或者该类(Thread类)的join()、join(long)、join(long, int)、sleep(long)或sleep(long, int)方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException

即 interrupt()将线程的冻结状态清除掉,让线程恢复到具备着CPU执行资格的状态(中断:终止运行,其实就是一种冻结状态)

(3)使用interrupt(),不用notify()也能唤醒被wait()的线程,不用等时间到也能唤醒被sleep()的线程。但interrupt()会抛出中断异常InterruptedException

3、当程序准备结束时,有可能会调用interrupt()强制地将某些线程从冻结状态拉回到运行状态,让其结束(让所有线程都结束,进程才可以结束)

四、守护线程setDaemon()

1、public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程(后台线程)。当正在运行的线程都是守护线程时,Java虚拟机退出。

注:该方法必须在启动线程前调用

2、上面的代码,让t0.interrupt()运行,将t1.interrupt()注释掉,程序不能停下来,因为t1还是活的。如果在开启t1(t1.start())前,将t1设置成守护线程t1.setDaemon(true),程序停了

3、守护线程(后台线程)的特点在于它和前台线程都正常进行开启,运行也一样,相互抢夺CPU的执行权。结束时不一样。结束时前台线程必须要进行手动结束(eg:以设置标志的形式将其结束),不结束线程会一直等待CPU的处理,消耗资源。而对于守护线程,如果所有的前台线程都结束了,守护线程无论处于什么状态,都自动结束

4、如果一个线程何时结束根据其他线程而定,就将该线程定义为守护线程

五、join()方法

1、public final void join() throws InterruptedException:等待该线程终止

2、何时使用join()方法?

      当在进行线程运算时,希望在运算过程中临时加入一个线程进行运算,此时就使用join()方法(临时加入一个线程运算时,可以使用join()方法)

      线程X执行到了线程A调用join()方法的这句话,线程X释放执行权,同时释放执行资格(相当于被冻结)。线程A和其他线程抢夺CPU的执行权执行,互不影响。等到线程A执行完毕,线程X才恢复执行资格(相当于被唤醒),才会继续抢夺CPU的执行权执行

class Demo implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "......" + i);
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();

        Thread t0 = new Thread(demo);
        Thread t1 = new Thread(demo);

        t0.start();
        //t0.join():t0线程申请加入。主线程释放CPU的执行权,同时释放执行资格。主线程等t0线程执行完终止后,才恢复执行资格,继续抢夺CPU的执行权执行
//        t0.join();
        t1.start();
        //如果将t0.join()放在此处,主线程释放CPU的执行权,同时释放执行资格。主线程等t0线程执行完终止后,才恢复执行资格,继续抢夺CPU的执行权执行
        //主线程释放的CPU的执行权,如果t1线程得到,t1执行,如果t0线程得到,t0执行(t0、t1互不影响)
        //join()方法只与执行此句话的线程(主线程)和调用join()方法的线程(t0线程)有关。执行此句话的线程必须让调用join()方法的线程先执行完毕,之后,执行此句话的线程再抢夺CPU的执行权执行
//        t0.join();

        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "...main..." + i);
        }
    }
}

3、join()方法也可以让线程处于冻结状态,因此,该方法也会抛出中断异常InterruptedException(也可以用interrupt()方法将被冻结的线程强制恢复过来)

六、线程组(java.lang.ThreadGroup)

1、线程组:将线程进行组的划分。可以通过调用线程组的方法,操作整个线程组中所有的线程

eg:void interrupt():中断此线程组中的所有线程

        void setDaemon(boolean daemon):更改此线程组的后台程序状态(将一个组的线程全设为后台线程)

2、线程在创建对象时,除了可以明确其任务外,还可以明确其名称、组

eg:Thread(ThreadGroup group, Runnable target):将线程存放到指定的组里去

七、创建线程的两种便捷方式

public class Test {
    public static void main(String[] args) {
        /**
         * 创建线程的便捷方式一
         */
        new Thread() {
            @Override
            public void run() {
                for (int x = 0; x < 50; x++) {
                    System.out.println(Thread.currentThread().getName() + "...x = " + x);
                }
            }
        }.start();

        /**
         * 创建线程的便捷方式二
         */
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int z = 0; z < 50; z++) {
                    System.out.println(Thread.currentThread().getName() + "...z = " + z);
                }
            }
        };
        new Thread(r).start();
    }
}

八、面试题

1、以下代码有无问题?如果有,发生在哪一行?

class Test implements Runnable {
    public void run(Thread t) {
        
    }
}

答:有问题,错误在第1行

编译器报错:Test不是抽象的,并且未覆盖java.lang.Runnable中的抽象方法run()

分析:run(Thread)是Test类的特有方法。Test类实现Runnable接口,但没有覆盖Runnable接口中的抽象方法。此时,Test类应该被定义为抽象类,即被abstract修饰

2、下面的代码运行结果是什么?

public class Test {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable run...");
            }
        }){
            @Override
            public void run() {
                System.out.println("subThread run...");
            }
        }.start();
    }
}

答:subThread run...

分析:以子类为主

public class Test {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable run...");
            }
        }){
            
        }.start();
    }
}

运行结果:runnable run...

分析:以任务为主

public class Test {
    public static void main(String[] args) {
        new Thread(){

        }.start();
    }
}

分析:以Thread自己的为主(线程子类调用父类的方法,因为没有覆盖)

总结:子类run() > 任务run() > Thread的run()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值