前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家java】Java里的进程、线程、协程 、Thread、守护线程、join线程的总结

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

作者头像
YourBatman
发布2019-09-03 13:49:19
7890
发布2019-09-03 13:49:19
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

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

进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.(比如一个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)。这个给一幅图,好好理解一下线程的状态转换:

线程状态转换图
线程状态转换图

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方法,则等待一定的时间。看个例子:
代码语言:javascript
复制
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中,像垃圾收集器线程就是守护线程。

说了这么方法之后,给出下面这幅图,就更加清晰了:

线程状态图
线程状态图

简要说说sleep和wait的区别:

sleep和wait的区别
sleep和wait的区别

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

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

join方法的使用场景:

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年07月20日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程的分类:用户线程和守护线程
  • 介绍Thread
    • join方法的使用场景:
    相关产品与服务
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档