说到线程,很多人最直观的感受就是多线程。本章不讨论高并发、多线程之类的。返璞归真,咱们来讨论讨论线程这个东西到底是什么东西,着眼于线程本身,我们怎么玩? 为了了解线程,这里我不得不搬出来线程的概念了:
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.(比如一个qq程序就是一个进程或者多个进程),系统进行资源分配的最小单位.。进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全 》》》》》》》》》》》》》》》》》》》 线程:程序执行流的最小单元(操作系统可识别的最小执行和调度单位)。进程是系统进行资源分配和调度的一个独立单位。也可以理解线程是一个程序里面不同的执行路径。是为了提高cpu的利用率而设计的。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据 》》》》》》》》》》》》》》》》》》》 协程(线程的线程):协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
虽然协程属于比较热门的一个话题,但是java视乎反应较慢,几乎没有什么应用(实现Coroutine来创建协程),最早应该是python里用得稍微多一点。但对于面向应用的java来说,貌似作用不大,了解一下即可
线程是轻量级的进程,它们是共享在父进程拥有的资源下,每个线程在父进程的环境中顺序的独立的执行一个活动,每个CPU核心在同一时刻只能执行一个线程,尽管我们有时感觉自己的计算机同时开着多个任务,其实他们每个的执行都是走走停停的,CPU轮流给每个进程及线程分配时间。
守护线程和用户线程的区别:当一个用户线程结束后,JVM会检查系统中是否还存在其他用户线程,如果存在则按照正常的调用方法调用。但是如果只剩守护线程而没有用户线程的话,JVM就会终止(从始至终都没有理睬守护线程)。 任何线程都可以是守护线程或者用户线程,所有线程一开始都是用户线程。 涉及守护线程的方法有两个:setDaemon( )和 isDaemon()。 Thread.setDaemon(false/true)设置为用户线程/守护线程;如果不设置该属性,默认为false。 需要注意的是:setDaemon()方法仅仅在线程对象已经被创建但是还没有运行前才能被调用,否则会报错。
线程从创建到最终的消亡,要经历若干个状态。一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。这个给一幅图,好好理解一下线程的状态转换:
Thread类实现了Runnable接口,在Thread类中,有一些比较关键的属性,比如name是表示Thread的名字,可以通过Thread类的构造器中的参数来指定线程名字,priority表示线程的优先级(最大值为10,最小值为1,默认值为5),daemon表示线程是否是守护线程,target表示要执行的任务。下面介绍几个常用方法:
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方法不能中断正在运行中的线程。所以结合isInterrupted方法,可以联合控制程序
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
说了这么方法之后,给出下面这幅图,就更加清晰了:
简要说说sleep和wait的区别:
线程的这么多方法中,此处我重点介绍一下join方法,因为有些场景,还真要他出马:
join底层是wait方法,所以它是会释放对象锁的,而sleep在同步的方法中是不释放对象锁的,只有同步方法执行完毕,其他线程才可以执行。
曾经做过一个联通的增值业务项目,其中有一个业务需要给联通方暴漏接口,他们调用我们的接口,我们进行业务处理后,再返回结果,接口要求是同步的,实时返回。如果异步的就可以用消息队列解决了,吧整个业务逻辑中比较费时间的都放在了子线程中运行,子线程跑完后在交由主线程返回结果。当时用的是java中的栅栏 CyclicBarrier ,现在想想用join也是可以实现的。