java高并发编程系列一:多线程基础

一.线程基础

1.一系列的任务在计算机中同时运行,比如写邮件的时候收件箱还能接收新的邮件,这就是并行。在单CPU的计算机中,其实并没有真正的并行,它是CPU时间片(cpu就是一个个的时间段)快速轮转调度使人产生的错觉,认为他们在同时运行,当然如果是多和cpu那么就可以真正的并行。

2.在计算机中,每一个任务就是一个进程process,在每一个进程内部至少有一个线程或者多个线程Thread,有时,线程也可称为轻量级的进程。

线程是程序执行的一个路径,每一个线程都有自己的局部变量表,程序计数器(指向正在执行的指令指针)以及线程的生命周期;现在操作系统中不止一个线程在运行,例如4核8线程(逻辑数);当启动了一个java虚拟机(JVM)时,从操作系统中就会创建一个新的进程-JVM进程,从JVM进程中可以派生和创建很多线程。

运行例子:实现并行运行

3.运行例子后,使用jdk自带的JVM工具jcosole查看当前的应用的控制台。

其中一个main线程,是在启动JVM时就启动了的进程创建的main线程,当然还有其他的守护线程,;例如垃圾回收线程,RMI线程;另一个Thread-0就是在程序中显示创建的线程。

二。线程的生命周期的五个状态;

每一个线程都有自己的局部变量表,程序计数器,以及生命周期,生命周期共有5个状态

1.NEW 状态:当我们调用了线程的strat方法就表示该线程已经启动了吗,答案是否定的,我们用关键字new 创建一个Thread对象时,此时它并不属于执行状态,因为没有调用start启动线程,它的状态就是NEW 状态,它是Thread对象的状态,它此时并不是一个线程,就是java 额普通对象;NEW状态通过调用start方法进去RUNNABLE可运行状态。

2.RUNNABLE状态:Thread对象进入RUNNABLE必须调用start方法,此时才会在JVM进程中创建了一个线程,并处于RUNNABLE状态,具备了执行的资格,等待CPU的的调度,并不会立即运行,线程和进程运行一样都要听令于CPU的调度。严格说,RUNNABLE线程只能意外终止或者进入RUNNING运行,即使在逻辑中调用WAIT,SLEEP或block的IO操作等,也必须先获得CPU的执行权才可以,由于存在running状态,所以不会直接进入BLOCKED和terminated状态。

3.RUNNING状态:一旦CPU时间片通过轮询或其他方式选中了执行线程,那么该线程此时才会真正的执行自己的逻辑代码,一个RUNNING的线程一定是RUNNABLE的,反过来则不成立。

在RUNNING中,其生命状态可以进行如下转换:

a调用jdk不推荐使用的stop方法,进入TERMINATED状态。

b调用sleep方法,wait方法加入waitset中,线程进入BLOCKED

c进行某个阻塞的IO操作,例如因数据的读取进入BLOCKED

d获取某个锁资源,从而加入该锁的阻塞队列中进入BLOCKED

e由于CPU时间调度轮询该线程进入放弃执行,进入RUNNABLE

f线程主动调用了yield,主动放弃的CPU执行权,不会优先执行,有CPU管理,进入RUNNABLE

4.线程的BLOCKED状态:上一点中说明了进入BLOCKED状态,在该状态中可切换至以下状态:

a调用jdk不推荐使用的stop方法,进入TERMINATED状态。

b线程阻塞结束或者完成指定时间的休眠,进入RUNNABLE

c wait中线程被其他线程notify、notifyall唤醒,进入RUNNABLE

d线程获取到某个锁资源,进入RUNNABLE

e线程在阻塞过程中被打断,例如其他线程调用interrupt方法,进入RUNNABLE

5.TERMINATED状态:这是一个线程的最终状态,不会继续切换到其他状态,整个生命周期结束了:

a线程正常结束

b线程运行出错意外结束

cJVNM Crash ---被杀死 导致所有线程结束

三Thread中的模板设计--线程启动start方法剖析

start方法启动了一个线程,进入RUNNABLE状态,start和run方式是什么关系呢

源码分析:

源码中意思是:当这个线程开始执行时,java虚拟机会调用此线程的run方法;结果是当前线程(调用start返回的)和另一个线程(主线程,执行run方法)两个线程同时在运行;不止一次的多次启动一个线程永远是不合法的,特别是一个线程一旦完成就不能在重新启动;如果线程已经启动就不能再次启动否则报出IllegalThreadStateException非法线程异常。

我们可以看到:

1线程在NEW状态时,threadStatus==0

2不能俩次启动线程,否则IllegalThreadStateException,TERMINATED状态不会回到RUNNABLE和RUNNING状态

3线程启动会加入到一个ThreadGroup中;

注:2次运行均报出了同一个错误,但导致异常的本质是不同的,第一次重复启动,处于Running时,第二个是运行结束TERMINATED,企图重新激活.

Thread中的模板设计;

其中,print方法类似于Thread的start方法,而wrapPrint则类似于run方法执行单元,这样的好处是程序结构由父类控制,并且是final修饰的,不允许修改,子类只需要实现想要的逻辑人物即可。

start是启动,run是执行单元。耦合度降低,控制更强了。

实例:Thread模拟营业大厅叫号机程序

一。Thread实现:

多线程下共享资源唯一性需要保证index号码是唯一的一个+static ,如果共享资源很多呢、如果资源需要经过一些复杂的运算呢,使用static会使变量生命周期变得很长,所以应该使用Runnable分离线程的控制和业务逻辑.

二.Runnable

注意:如果运行多次有时会出现多次,或者51号等问题,或者把max最大值增大到500,还会有的号码不出现出现等很多错误的情况,这是因为共享资源index存在线程安全的问题,多个线程之间的数据没有同步.,后面会讲到数据同步.

二,线程的命名

在线程较多的程序程序中为线程起一个有特殊意义的名字有助于问题的排查和跟踪.线程的默认名字是"Thread-",作为前缀和一个自增数字进行组合.

三线程的父子关系

每一个创建线程的构造方法中都会调用init方法,查看发现每一个新创建的线程都会有一个父线程。currentThread();会获取当前的线程,我们知道线程最初的状态为NEW状态,没有执行start方法之前,他只是一个普通实例,并不是一个线程,那么currentThread();就是创建它的那个线程,总结如下:

1一个线程的创建肯定是由另一个线程完成

2倍创建线程的父线程就是创建它的线程

3main函数所在的线程是由JVM创建的,也就是main线程,那么说明所有线程的父线程都是main线程。

在线程的构造函数中可以显示指定一个组ThradGroup,如果没有指定组。子线程将会加入父线程所在的线程组。

那么我们知道,默认情况下子线程会继承父线程的守护线程,优先级,线程组等特性。

在Thread的构造参数中有一个stackSize参数,通过官方文档发现,stack参数越大,则代表着正在线程内方法调用递归层次越深,值越小代表着创建的线程数量更多。在有些平台下,越高的stack设定,可以允许的递归深度越多,反之,越少的stack设定,则递归深度越浅。

不同环境下的测试数据:

四.守护线程

守护线程是一类比较特殊的线程,一般用于处理一些后台的工作也叫后台线程,比如jdk的垃圾回收线程。

当非守护线程结束了,守护线程也会结束的,也就是说JVM中没有一个非守护线程,那么JVM会退出,守护线程具备自动结束生命周期的特性。而非守护线程不具备这样的特点。

如果打开守护线程的注释,当main线程执行完毕以后,守护线程自动结束,JVM也退出。

设置守护线程,必须在线程启动之前通过setDaemon设置守护线程才生效,通过isDaemon判断线程是不是守护线程,

线程中是否是守护线程和父线程也有关系,如果父线程是正常线程,则子线程也是正常线程,反之亦然。

当你希望关闭某些线程的时候,或者退出JVM进程的时候,一些线程能够自动关闭,这时可以考虑使用守护线程。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181201G039PM00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励