一. 线程入门
一. 线程入门
1. 进程和多线程简介
1.1 何为进程?
- 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
- 一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
- 进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序
1.2 何为线程?
- 线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。
- 与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
- 线程是在同一程序内几乎同时执行一个以上的程序段。
1.3 线程和进程的关系
- 基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
- 从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
- 线程上下文的切换比进程上下文切换要快很多
- 进程切换时,涉及到当前进程的CPU环境的保存和新被调度运行进程的CPU环境的设置。
- 线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。
1.4 线程有哪些基本状态?这些状态是如何定义的?
- 新建(new):新创建了一个线程对象。
- 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。
- 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
- 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice(时间片),暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种:
- (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waiting queue)中。
- (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步 锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
- (三). 其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
- 死亡(dead):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

1.5 线程的优先级
每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。
- 线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。
- 线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。
Thread类中包含的成员变量代表了线程的某些优先级。
- 如Thread.MIN_PRIORITY(常数1),Thread.NORM_PRIORITY(常数5),
Thread.MAX_PRIORITY(常数10)。 - 其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1) 到Thread.MAX_PRIORITY(常数10) 之间。
- 在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)。
1.6 何为多线程?
多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。
1.7 为什么多线程是必要的?
开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能
1.8 为什么提倡多线程而不是多进程?
线程就是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。
2. 几个重要的概念
2.1 同步和异步
同步和异步通常用来形容一次方法调用。
- 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
- 异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。
2.2 并发(Concurrency)和并行(Parallelism)
它们都可以表示两个或者多个任务一起执行,但是偏重点有些不同。
并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。
而并行是真正意义上的"同时执行"。
- 多线程在单核CPU的话是顺序执行,也就是交替运行(并发)。
- 多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行(并行)。
2.3 高并发
高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。
2.4 临界区
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。
但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。在并行程序中,临界区资源是保护的对象。
2.5 阻塞和非阻塞
非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回,而阻塞与之相反。
3. 多线程
3.1 使用多线程常见的三种方式
- 继承Thread类
- 实现Runnable接口
- 使用线程池
3.2 Java多线程分类
- 用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
- 守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 "佣人"。
特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程
最常见的守护线程:垃圾回收线程
3.3 如何设置守护线程?
- 通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程
1. setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
2. 在守护线程中产生的新线程也是守护线程
3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
4. 一些常用方法
4.1 currentThread()
返回对当前正在执行的线程对象的引用。
4.2 getId()
返回此线程的标识符
4.3 getName()
返回此线程的名称
4.4 getPriority()
返回此线程的优先级
4.5 isAlive()
测试这个线程是否还处于活动状态。
什么是活动状态呢?
活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。
4.6 sleep(long millis)
使当前正在执行的线程以指定的毫秒数"休眠"(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
4.7 interrupt()
中断这个线程。
4.8 interrupted() 和isInterrupted()
interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能
isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志
4.9 setName(String name)
将此线程的名称更改为等于参数 name 。
4.10 isDaemon()
测试这个线程是否是守护线程。
4.11 setDaemon(boolean on)
将此线程标记为 daemon线程或用户线程。
4.12 join()
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
join()的作用是:"等待该线程终止",这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
4.13 yield()
yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。
4.14 setPriority(int newPriority)
更改此线程的优先级