zoukankan      html  css  js  c++  java
  • Java线程的启动与中止

    一、线程与进程的关系


    关于进程与线程,百度百科上是这样描述的:

    进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

    线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    简单的总结一下,就变成了下面的结果:

    进程(Process): 程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源。

    线程(thread) : CPU调度的最小单位,必须依赖进程而存在。

    也就是说 线程进程的关系,就像玄幻小说中虫族的母体和子体一样,子体离开了母体便不能存活。线程亦是如此,必须依赖于进程而存在。

    二、Java线程的启动方式


    关于Java的线程,我们首当其冲的会想到java.lang.Thread类,这是一种最简单的创建线程的方式,我们只需要通过继承Thread类就可以实现:

    /**
     * @author cai
     * 通过继承Thread类的方式
     */
    private static class UserThread extends Thread{
    
        /**
         * 重写  Thread 类的  run() 方法
         */
        @Override
        public void run() {
            System.out.println("this is a thread for extends Thread");
        }
    }
    

    这里,我们重写了Thread类中的run()方法,并打印一条语句来表示线程的启动方式,现在我们来测试一下:

    public static void main(String[] args) {
    
        // 继承Thread类的方式
        Thread thread = new UserThread();
        thread.start();
    }
    

    控制台的打印结果:

    this is a thread for extends Thread
    

    从打印结果可以看出,我们的线程正常的启动,中间没有出现意外。除了这种方式之外,JDK内部又给我们提供了一个接口类:java.lang.Runnable,同样,我们也可以通过实现该接口,重写run()方法来启动一个新的线程:

    /**
     * @author cai
     * 通过实现Runnable接口的方式
     */
    private static class UserRunnable implements Runnable{
    
        /**
         * 重写  Runnable 类的  run() 方法
         */
        @Override
        public void run() {
            System.out.println("this is a thread for implements Runnable");
        }
    }
    

    这里我们同样打印一句话来表示此线程的启动方式,现在来测试一下:

    public static void main(String[] args) throws  {
        // 实现Runnable接口的方式
        // 这里注意:所有线程的启动,都必须通过调用Thread类中start()方法来实现
        Runnable runnable = new UserRunnable();
        new Thread(runnable).start();
    }
    

    相信上面的代码,小伙伴们都能看懂(多注意一下第二行的注释,这是重点),现在来看看控制台的打印结果:

    this is a thread for implements Runnable
    

    同理,这里的线程也正常的启动了。但看到这里,小伙伴们可能就会产生疑问,为什么JDK要多此一举,提供了一种Thread类后,为什么还要提供Runnable接口类呢?

    在这里给有这个疑惑的小伙伴们答疑下:

    因为Java是单继承,只能继承一个类,不能继承多个类。而在我们某些实际的业务中,我们可能需要继承一个类来处理逻辑业务,那么就不能再继承Thread类来处理线程相关的操作了。所以JDK又为我们提供了一个实现Runnable接口的方式,而且在Java中,一个类是可以实现多个接口的,这样我们在使用第二种方式处理线程就不会有顾忌了。(这里比较有意思的是:Thread类实现了Runnable接口,有兴趣的小伙伴可以去看一下源码。)

    那么除了上面的两种方式之外,Java是否提供了第三种方式呢?答案是肯定的,从JDK1.5开始,JDK为我们提供了一个新的接口类:java.util.concurrent.Callable,我们可以通过实现这个接口来启动一个新得线程,而这种方式与实现Runnable接口来启动线程所不同的是,它会带有一个返回值,我们来看一下代码:

    /**
     * @author cai
     * 通过实现Callable接口的方式
     * 带返回值
     */
    private static class UserCallable implements Callable<String>{
    
        /**
         * 重写  Callable 类的  run() 方法
         * @return
         * @throws Exception
         */
        @Override
        public String call() throws Exception {
            return "this is a thread for implements Callable(return String).";
        }
    }
    

    测试一下:

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 实现Callable接口的方式   带返回值
        UserCallable callable = new UserCallable();
        FutureTask<String> future = new FutureTask<String>(callable);
        new Thread(future).start();
        System.out.println(future.get());
    }
    

    我们这里将返回值打印一下:

    this is a thread for implements Callable(return String).
    

    可以看出,我们的线程正常的启动,没有问题。

    那么看了以上三种Java线程的启动方式,相信肯定有很多小伙伴会好奇,如果我要中止一个线程,我需要怎么做呢?让我们来一起看看吧。

    三、Java线程的中止


    怎样让一个正在运行的线程安全的停止工作呢?这里有两种方法:

    1、线程自然的终止:程序正常的执行完或者抛出未处理的异常。

    程序正常的执行完就不必再说,以上的代码都属于此类,我们来看一看抛出未处理异常的情况,这里我们将上述实现Runnable接口来启动线程的代码修改一下:

    /**
     * @author cai
     * 通过实现Runnable接口的方式
     */
    private static class UserRunnable implements Runnable{
    
        /**
         * 重写  Runnable 类的  run() 方法
         */
        @Override
        public void run() {
            // 重点加了这样的一行代码
            int i = 10 / 0;
            System.out.println("this is a thread for implements Runnable");
        }
    }
    

    这里我们加了一行代码,小伙伴们应该都可以看懂,这行代码是必定会抛出异常的,但我们又没有对异常进行处理,现在来看一下控制台的打印结果:

    Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    	at com.cai.thread.create.NewThread$UserRunnable.run(NewThread.java:39)
    	at java.lang.Thread.run(Thread.java:748)
    

    从结果可以看出,程序运行到了int i = 10 / 0这里就停止了,并没有打印出下一行的结果,由此可见,线程到了这里便停止了,没有再走下去。

    2、调用JDK为我们提供的一系列方法

    stop(),resume(),suspend(),interrupt(),isInterrupted(),interrupted()这些方法都是JDK为我们提供的,但是``stop(),resume(),suspend()`已经不建议使用了,原因如下:

    stop() : 会导致线程不会正常的释放资源,“恶意”的中断当前正在运行的线程,不管线程的逻辑是否完整。

    suspend()resume() : 极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象,产生死锁。(这个例子以后在讲线程锁的时候会解释。)

    我们先来看一下调用stop()的例子:

    /**
     * @author cai
     *  调用 stop() 方法的例子
     */
    private static class UserRunnable implements Runnable{
    
        @Override
        public void run() {
            try {
                // 让此线程休眠1秒钟
                Thread.sleep(1000);
            }catch (Exception e){
                // 异常 捕获处理
            }
            // 此处不会运行,控制台不会打印
            // 若实际开发中,这里要处理一个很重要的业务逻辑,那么这里就会有很大的问题。
            // 所以不建议使用
            System.out.println("代码到此处不会运行");
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new UserRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        // 强行中止线程
        // 从这里可以看出,JDK已经不建议使用stop()方法了,添加了@Deprecated注解
        thread.stop();
    }
    

    我这里将测试代码也一并贴了上去,可以在代码的注释中得到相关的结果。讲完这些,我们来看看剩下的三个方法:interrupt(),isInterrupted(),interrupted()

    interrupt():调用此方法会中断一个线程,但并不是强行关闭这个线程,只是先给这个线程打个招呼,同时将线程的中断标志位设置为true,线程是否中断,由线程本身决定。

    isInterrupted():判断当前的线程是否处于中断状态(返回true或者false)。

    interrupted():静态方法,判断当前的线程是否处于中断状态,同时将中断的标志位设置为false

    注意:如果方法中抛出了InterruptedException异常,那么线程的中断标志位会被复位成false,如果确实需要中断线程的操作,我们需要在catch语句中再次调用interrupt()方法。

    解释完了,直接上代码吧:

    /**
     * @author cai
     * 调用 interrupt(),isInterrupted(),interrupted() 方法的例子
     */
    private static class UserThread extends Thread{
    
        // 给线程一个名字,创建对象时赋值
        public UserThread(String threadName) {
            super(threadName);
        }
    
        @Override
        public void run() {
            // 获得线程的名字
            String threadName = Thread.currentThread().getName();
           try {
               // @2
               System.out.println(threadName+" flag is " + isInterrupted());
               // 休眠一秒钟   需要捕获异常 InterruptedException  @3
               Thread.sleep(1000);
           }catch (InterruptedException e){
               // 打印一下  isInterrupted() 的状态  @4
               System.out.println(threadName+" catch interrput flag is " + isInterrupted());
               // 调用 interrupt() 方法  中断线程操作  @5
               interrupt();
           }
            // 打印线程的名字  @6
            System.out.println(threadName+" interrput flag is " + interrupted());
            // @7
            System.out.println(threadName+" interrput flag is " + isInterrupted());
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // interrupt(),isInterrupted(),interrupted()  演示
        Thread thread = new UserThread("caiThread");
        thread.start();
        // @1
        thread.interrupt();
    }
    

    这里为了方便解释,我分了步骤:

    1、@1 的位置调用的interrupt()方法,所以这里的标志位是true,所以 @2 的位置打印结果为true

    2、@3 位置的sleep方法会抛出InterruptedException异常,我这里捕获了,在看之前的理论,抛出此异常,标志位会重置为false,所以@4 这里的打印结果为false

    3、@5 位置再次调用了interrupt(),又把标志位改为了false,所以 @6 这里打印的结果为true,但是这里注意的是,@6 调用了interrupted()这个静态方法,所以标志位又变为了false,所以@7 打印的结果为false

    控制台打印结果:

    caiThread catch flag is true
    caiThread catch interrput flag is false
    caiThread interrput flag is true
    caiThread interrput flag is false
    

    小伙伴们可以通过对比结果、代码和解释一起看,相信还是很容易明白的。

    对线程的了解再多一点点


    Java线程总归下来有五种状态:

    新建、就绪、阻塞、运行、死亡

    而这里对应的方法却有很多种,具体的关系,我这里准备了一张图:

    image-20200526162456870-Java线程状态图

    这张图上面的各种方法我都会在下次的文章中分享,这次的分享就到这里,希望大家能够喜欢。

    最后


    :若有转载,请标明原处。如若有错,也欢迎小伙伴前来指正。

  • 相关阅读:
    LeetCode题解之Flipping an Image
    LeetCode 之Find Minimum in Rotated Sorted Array
    LeetCode题解Transpose Matrix
    LeetCode 题解之Minimum Index Sum of Two Lists
    LeetCode题解之Intersection of Two Linked Lists
    LeetCode 题解之Add Two Numbers II
    LeetCode题解之Add two numbers
    href="#"与href="javascript:void(0)"的区别
    有关ie9 以下不支持placeholder属性以及获得焦点placeholder的移除
    ie7下属性书写不规范造成的easyui 弹窗布局紊乱
  • 原文地址:https://www.cnblogs.com/caimm/p/12966571.html
Copyright © 2011-2022 走看看