(二) 线程创建、中止、中断、线程Join、优先级、调度

线程的基本创建

import java.util.concurrent.ExecutionException;

/**
 * @author monco
 * @date 2020/5/18
 * @description: 创建新线程
 */
public class CreateThread {

    /**
     * 继承 Thread 类 实现新线程创建
     */
    private static class UseThread extends Thread {
        // 重写父类方法
        @Override
        public void run() {
            super.run();
            System.out.println("i am a new Thread");
        }
    }

    private static class UseRunnable implements Runnable {
        // 实现接口
        public void run() {
            System.out.println("I am implements Runnable");
        }
    }

    public static void main(String[] args)
            throws InterruptedException, ExecutionException {
        // 适用创建新线程的start方法
        UseThread useThread = new UseThread();
        useThread.start();
        // 适用接口需要将实现 Runnable 对象的类 作为参数 传入 Thread对象中
        // Runnable 接口只有一个run方法 没有 start方法  实际还是适用 Thread 对象的start方法
        UseRunnable useRunnable = new UseRunnable();
        new Thread(useRunnable).start();
    }
}


线程的中止

  • 自然两种情况:
    1.线程中的方法执行完毕
    2.抛出了一个未处理的异常导致线程提前结束
  • 手动中止
    暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。
    但是这些API是过期的,也就是不建议使用的。
    不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。
    同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
    正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

线程的中断

public class EndThread {
    private static class UseThread extends Thread {
        // 构造方法 传入线程名称
        public UseThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            // 获得当前线程名称
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " interrupt flag =" + isInterrupted());
            System.out.println("静态方法:调用当前线程的interrupted 方法 会重置中断标志位false " + Thread.interrupted());
            // 如果while 循环中的 判断条件是 Thread.interrupted() 的话 那么最终打印的中断标志位 是 false
            // 如果while 循环中的 判断条件是 isInterrupted() 的话 那么最终打印的中断标志位是 true
            while (!isInterrupted()) {
                System.out.println(threadName + " is running");
            }
            System.out.println(threadName + " interrupt flag =" + isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("endThread");
        endThread.start();
        Thread.sleep(10);
        // 注意:处于死锁状态的线程无法被中断
        endThread.interrupt();
    }
}

线程 sleep()

public class SleepThread {

    public static class ThreadRun extends Thread {
        @Override
        public void run() {
            int i = 90;
            while (i > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                }
                System.out.println("I am " + Thread.currentThread().getName()
                        + " and now the i=" + i--);
            }
        }
    }

    public static void main(String[] args) {
        ThreadRun threadRun = new ThreadRun();
        threadRun.setName("threadRun");
        threadRun.start();
    }
}


线程 sleep() 会释放锁吗?

public class  SleepLock {

    private Object lock = new Object();

    public static void main(String[] args) {
        SleepLock sleepTest = new SleepLock();
        Thread threadA = sleepTest.new ThreadSleep();
        threadA.setName("ThreadSleep");
        Thread threadB = sleepTest.new ThreadNotSleep();
        threadB.setName("ThreadNotSleep");
        threadA.start();
        try {
            Thread.sleep(1000);
            System.out.println(" Main slept!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadB.start();
    }


    private class ThreadSleep extends Thread {

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " will take the lock");
            try {
                synchronized (lock) {
                    System.out.println(threadName + " taking the lock");
                    Thread.sleep(5000);
                    System.out.println("Finish the work: " + threadName);
                }
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
        }
    }

    private class ThreadNotSleep extends Thread {

        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " will take the lock time=" + System.currentTimeMillis());
            synchronized (lock) {
                System.out.println(threadName + " taking the lock time=" + System.currentTimeMillis());
                System.out.println("Finish the work: " + threadName);
            }
        }
    }
}

输出结果

ThreadSleep will take the lock
ThreadSleep taking the lock
 Main slept!
ThreadNotSleep will take the lock time=1590040836717
Finish the work: ThreadSleep
ThreadNotSleep taking the lock time=1590040840718
Finish the work: ThreadNotSleep

结论

线程的sleep方法是不会释放锁的,会持有当前线程的资源。

其他线程方法

yield()方法:使当前线程让出CPU占有权,但让出的时间是不可设定的,也不会释放锁资源。
注意:并不是每个线程都需要这个锁的,而且执行yield()的线程不一定就会持有锁,我们完全可以在释放锁后再调用yield方法。
所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中马上又被执行。


线程的 Join()

Join()方法主要作用是用来向当前线程中加其他线程任务 可以理解为强行插队

public class UseJoin {

    static class Goddess implements Runnable {
        // 插队线程
        private Thread thread;

        public Goddess(Thread thread) {
            this.thread = thread;
        }

        public Goddess() {
        }

        public void run() {
            System.out.println("monco 喜欢的女孩 Goddess开始排队打饭.....");
            try {
                if (thread != null) {
                    thread.join();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SleepTools.second(2);
            System.out.println(Thread.currentThread().getName()
                    + " monco 喜欢的女孩 Goddess打饭完成.");
        }
    }

    static class GoddessBoyfriend implements Runnable {

        public void run() {
            SleepTools.second(2);
            System.out.println("Goddess Boyfriend开始排队打饭.....");
            System.out.println(Thread.currentThread().getName()
                    + " Goddess Boyfriend打饭完成.");
        }
    }

    public static void main(String[] args) throws Exception {
        // 情敌对象线程
        GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
        Thread gbf = new Thread(goddessBoyfriend);
        // 喜欢的女孩对象 传入情敌线程  情敌线程会插队 到 女孩线程
        Goddess goddess = new Goddess(gbf);
        Thread g = new Thread(goddess);
        g.start();
        gbf.start();
        System.out.println("monco开始排队打饭.....");
        // 女孩线程插队到主线程
        g.join();
        //让主线程休眠2秒
        SleepTools.second(2);
        System.out.println(Thread.currentThread().getName() + " monco打饭完成.");
    }
}

输出结果

monco开始排队打饭.....
monco 喜欢的女孩 Goddess开始排队打饭.....
Goddess Boyfriend开始排队打饭.....
Thread-0 Goddess Boyfriend打饭完成.
Thread-1 monco 喜欢的女孩 Goddess打饭完成.
main monco打饭完成.

线程的调度

线程调度是指系统为线程分配CPU使用权的过程,主要调度方式有两种:

协同式线程调度(Cooperative Threads-Scheduling)

抢占式线程调度(Preemptive Threads-Scheduling)

使用协同式线程调度的多线程系统,线程执行的时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。使用协同式线程调度的最大好处是实现简单,由于线程要把自己的事情做完后才会通知系统进行线程切换,所以没有线程同步的问题,但是坏处也很明显,如果一个线程出了问题,则程序就会一直阻塞。
使用抢占式线程调度的多线程系统,每个线程执行的时间以及是否切换都由系统决定。在这种情况下,线程的执行时间不可控,所以不会有「一个线程导致整个进程阻塞」的问题出现。
在Java中,Thread.yield()可以让出CPU执行时间,但是对于获取,线程本身是没有办法的。对于获取CPU执行时间,线程唯一可以使用的手段是设置线程优先级,Java设置了10个级别的程序优先级,当两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
Java中的线程优先级是通过映射到操作系统的原生线程上实现的,所以线程的调度最终取决于操作系统,操作系统中线程的优先级有时并不能和Java中的一一对应,所以Java优先级并不是特别靠谱。
Java中的线程是通过映射到操作系统的原生线程上实现的,所以线程的调度最终取决于操作系统,而操作系统级别,OS是以抢占式调度线程,我们可以认为线程是抢占式的。Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。而且操作系统中线程的优先级有时并不能和Java中的一一对应,所以Java优先级并不是特别靠谱。但是在Java中,因为Java没有提供安全的抢占式方法来停止线程,要安全的停止线程只能以协作式的方式。

线程的优先级

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。

posted @ 2020-05-21 14:38  一南  阅读(263)  评论(0编辑  收藏  举报