【小家java】Java里的进程、线程、协程 、Thread、守护线程、join线程的总结

相关阅读

【小家java】java5新特性(简述十大新特性) 重要一跃
【小家java】java6新特性(简述十大新特性) 鸡肋升级
【小家java】java7新特性(简述八大新特性) 不温不火
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java9新特性(简述十大新特性) 褒贬不一
【小家java】java10新特性(简述十大新特性) 小步迭代
【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本


说到线程,很多人最直观的感受就是多线程。本章不讨论高并发、多线程之类的。返璞归真,咱们来讨论讨论线程这个东西到底是什么东西,着眼于线程本身,我们怎么玩?
为了了解线程,这里我不得不搬出来线程的概念了:

进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.(比如一个qq程序就是一个进程或者多个进程),系统进行资源分配的最小单位.。进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全
》》》》》》》》》》》》》》》》》》》
线程:程序执行流的最小单元(操作系统可识别的最小执行和调度单位)。进程是系统进行资源分配和调度的一个独立单位。也可以理解线程是一个程序里面不同的执行路径。是为了提高cpu的利用率而设计的。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据
》》》》》》》》》》》》》》》》》》》
协程(线程的线程):协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快


虽然协程属于比较热门的一个话题,但是java视乎反应较慢,几乎没有什么应用(实现Coroutine来创建协程),最早应该是python里用得稍微多一点。但对于面向应用的java来说,貌似作用不大,了解一下即可


线程是轻量级的进程,它们是共享在父进程拥有的资源下,每个线程在父进程的环境中顺序的独立的执行一个活动,每个CPU核心在同一时刻只能执行一个线程,尽管我们有时感觉自己的计算机同时开着多个任务,其实他们每个的执行都是走走停停的,CPU轮流给每个进程及线程分配时间。

线程的分类:用户线程和守护线程
  • 用户线程:是用户创建的一般线程,如继承Thread类或实现Runnable接口等实现的线程。
  • 守护线程:是为用户线程提供服务的线程,如JVM的垃圾回收、内存管理等线程。

守护线程和用户线程的区别:当一个用户线程结束后,JVM会检查系统中是否还存在其他用户线程,如果存在则按照正常的调用方法调用。但是如果只剩守护线程而没有用户线程的话,JVM就会终止(从始至终都没有理睬守护线程)。
任何线程都可以是守护线程或者用户线程,所有线程一开始都是用户线程。
涉及守护线程的方法有两个:setDaemon( )和 isDaemon()。
Thread.setDaemon(false/true)设置为用户线程/守护线程;如果不设置该属性,默认为false。
需要注意的是:setDaemon()方法仅仅在线程对象已经被创建但是还没有运行前才能被调用,否则会报错。

介绍Thread

线程从创建到最终的消亡,要经历若干个状态。一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。这个给一幅图,好好理解一下线程的状态转换:
【小家java】Java里的进程、线程、协程 、Thread、守护线程、join线程的总结_第1张图片
Thread类实现了Runnable接口,在Thread类中,有一些比较关键的属性,比如name是表示Thread的名字,可以通过Thread类的构造器中的参数来指定线程名字,priority表示线程的优先级(最大值为10,最小值为1,默认值为5),daemon表示线程是否是守护线程,target表示要执行的任务。下面介绍几个常用方法:

  • start方法:用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
  • run方法:run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务
  • sleep方法:相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。(但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象)。当上面一个线程sleep,它不会释放锁,后面的线程都将会被阻塞。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
  • yield方法:它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
  • join方法:join() join(long millis) 假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间。看个例子:
public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("myThread");
        myThread.start();
        try {
            System.out.println("线程" + Thread.currentThread().getName() + "等待");
            myThread.join();
            System.out.println("线程" + Thread.currentThread().getName() + "继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyThread extends Thread {

        @Override
        public void run() {
            System.out.println("进入线程" + Thread.currentThread().getName());
            try {
                Thread.currentThread().sleep(5000); //模拟执行任务需要5秒
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("线程" + Thread.currentThread().getName() + "执行完毕");
        }
    }
输出:
线程main等待
进入线程myThread
线程myThread执行完毕
线程main继续执行

由此可以看出,join线程阻断了main线程,执行完成之后,main线程才继续。(实际上调用join方法是调用了Object的wait方法,这个可以通过查看源码得知)由于wait方法会让线程释放对象锁,所以join方法同样会让线程释放对一个对象持有的锁。

  • interrupt方法:顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。

通过interrupt方法可以中断处于阻塞状态的线程,interrupt方法不能中断正在运行中的线程。所以结合isInterrupted方法,可以联合控制程序

  • stop方法:stop方法已经是一个废弃的方法,它是一个不安全的方法。因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。所以stop方法基本是不会被用到的。
  • destroy方法:destroy方法也是废弃的方法。基本不会被使用到。
  • getId、getName、setName、currentThread()
  • getPriority和setPriority:用来获取和设置线程优先级。
  • setDaemon和isDaemon:用来设置线程是否成为守护线程和判断线程是否是守护线程。

守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

说了这么方法之后,给出下面这幅图,就更加清晰了:
【小家java】Java里的进程、线程、协程 、Thread、守护线程、join线程的总结_第2张图片

简要说说sleep和wait的区别:
sleep和wait的区别

线程的这么多方法中,此处我重点介绍一下join方法,因为有些场景,还真要他出马:

join底层是wait方法,所以它是会释放对象锁的,而sleep在同步的方法中是不释放对象锁的,只有同步方法执行完毕,其他线程才可以执行。

join方法的使用场景:

曾经做过一个联通的增值业务项目,其中有一个业务需要给联通方暴漏接口,他们调用我们的接口,我们进行业务处理后,再返回结果,接口要求是同步的,实时返回。如果异步的就可以用消息队列解决了,吧整个业务逻辑中比较费时间的都放在了子线程中运行,子线程跑完后在交由主线程返回结果。当时用的是java中的栅栏 CyclicBarrier ,现在想想用join也是可以实现的。

你可能感兴趣的:(享学Java)