每日一诗
忆江上吴处士
——贾岛
闽国扬帆去,蟾蜍亏复圆。
秋风生渭水,落叶满长安。
此地聚会夕,当时雷雨寒。
兰桡殊未返,消息海云端。
一、进程与线程
进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
二、影响服务器吞吐量的因素
- 硬件:CPU、内存、磁盘、网络
- 软件:最大化的利用硬件资源
线程数量、JVM内存分配大小、网络通信机制(BIO、NIO、AIO)、磁盘IO - 线程数量提升服务端的并发数量
三、并法与并行
并行是指两个或者多个事件在同一时刻发生;
并发是指两个或多个事件在同一时间间隔内发生,这个词可以冲宏观和微观两个层面来讲,如果从微观
角度来看。以线程为例,假设当前电脑的cpu是单核,但是能不能支持多线程呢?当然也是能的,此时
如果是多线程运行的话,那么CPU是通过不断分配时间片的方式来实现线程切换,由于切换的速度足够
快,我们很难感知到卡顿的过程。
四、java中的线程
4.1线程的三种方式
- Runnable接口
- Tread类
- Callable/Future 带返回值的
4.2 Thread这个工具在哪些场景可以应用
- 网络请求分发的场景
- 文件导入
- 短信发送场景
四、线程的基础
4.1 java线程状态(6种)与操作系统线程状态(5种)
4.1.1 Java 线程中几个状态说明:
定义在Thread类中的 State枚举中,可以直接查看代码中的注释
java.lang.Thread
. State . NEW | RUNNABLE | BLOCKED | WAITING | TIMED_WAITING | TERMINATED
状态 | 解释 | 说明 |
---|---|---|
NEW | 新建一个线程对象,还没有调用start()方法 | new一个线程实例,线程还没有start |
RUNNABLE | Java线程中将就绪(READY)和运行中(RUNNING)两种状态统一为 RUNABLE | 等待被调度或正在运行中 |
BLOCKED | 表示线程阻塞于锁 | 线程阻塞在进入synchronized修饰的方法或代码块时的状态 |
WAITING | 进行该状态的线程需要等待其他线程做出一些特定动作(通知或中断) | 让出CPU,等待被显示的唤醒,被唤醒后进入BLOCKED状态,重新获取锁 |
TIMED_WAITING | 不同于waiting,它可以在指定的时间后自行返回 | 超时等待,让出CPU,时间到了自动唤醒,进入BLOCKED状态,重新获取锁 |
TERMINATED | 表示显示已经执行完毕 |
RUNNABLE
Java中 RUNNABLE 对应操作系统的几个状态
READY
- 有资格运行,但还没有被调度
- 调用线程的 start()方法,进入就绪状态
- 当前线程 sleep() 方法结束,其他线程 join() 结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态
- 当前线程时间片用完了,调用当前线程的 yield()方法,当前线程进入就绪状态
- 锁池里的线程拿到对象锁后,进入就绪状态
RUNNING
线程调度程序选中该线程进行运行
操作系统线程阻塞等待 I/O
JVM 并不关心操作系统线程的实际状态,从 JVM 看来,等待CPU使用权(操作系统状态为可运行态)与等待 I/O(操作系统处于等待状态)没有区别,都是在等待某种资源,所以都归入RUNNABLE 状态
TERMINATED
线程 run()方法完成时,或者主线程main()方法结束时,就认为它终止了。
这个线程对象也许是活的,但是已经不是一个单独执行的线程了,线程一旦终止了就不能复生,在一个终止的线程上调用 start()方法,会抛出
java.lang.IllegalThreadStateException
异常BLOCKED
由synchronized锁导致进入该状态(WAITING,TIMED_WAITING状态下唤醒也可能进入该状态)
(Java中的 BLOCKED状态与操作系统中的 阻塞状态不同)
WAITING
各情况下进入该状态,线程不会占用CPU,线程会让出锁,等待被其他线程唤醒,然后会进入 BLOCKED 状态,重新竞争锁。
TIMED_WAITING
各个情况下进入该状态,线程不会占用CPU,时间到了自动唤醒
如果是因为sleep进入该状态,线程不会释放锁,等到时间到了自动唤醒进入RUNNABLE状态
其他情况下线程会释放锁,等待其他线程唤醒,超时时间到了自动唤醒,然后进入 BLOCKED状态,重新竞争锁
4.1.2 java中的线程与操作系统中线程的区别
操作系统中进程(线程)的状态有:
初始状态(NEW)
对应 Java中的
可运行状态(READY)
对应 Java中的 RUNNBALE 状态
运行状态(RUNNING)
对应 Java中的 RUNNBALE 状态
等待状态(WAITING)
该状态在 Java中被划分为了 BLOCKED,WAITING,TIMED_WAITING 三种状态
当线程调用阻塞式 API时,进程(线程)进入等待状态,这里指的是操作系统层面的。从 JVM层面来说,Java线程仍然处于 RUNNABLE 状态。
JVM 并不关心操作系统线程的实际状态,从 JVM 看来,等待CPU使用权(操作系统状态为可运行态)与等待 I/O(操作系统处于等待状态)没有区别,都是在等待某种资源,所以都归入RUNNABLE 状态。
终止状态 (TERMINATED)
4.2 Java 的线程运行周期图
上图还有些欠缺,例如:
- WAITING和TIMED_WATING,在唤醒后,如果有锁,是有可能进入到阻塞状态等待锁的
- 缺少了一个阻塞状态获取锁后变成就绪状态
- 线程由运行到终止态,除了线程运行结束,还应加上stop()、interrupt()
需要注意的是,操作系统中的线程除去 new 和 terminated 状态,一个线程真实存在的状态,只有:
- ready :表示线程已经被创建,正在等待系统调度分配CPU使用权。
- running :表示线程获得了CPU使用权,正在进行运算
- waiting :表示线程等待(或者说挂起),让出CPU资源给其他线程使用
在加上新建状态和死亡状态,一共5种。
疑问: LockSupport.unpark()是干啥的?
——后续再研究
4.3线程的启动的时候做了什么
- 调用start()方法,实际上java会调用 native start0()方法。
- start0()方法会调用JVM里的c++方法,后续会通过create_thread方法和start_thread方法
- create_thread方法会在系统中创建一个线程
start_thread会在系统中启动线程,在线程调度的时候,会调用系统中线程的调用算法,分配给不同的CPU - 而后,系统会调用jvm线程的run方法,在调用对应java层面的run方法
- 在线程调用结束后,jvm会做销毁动作
4.4线程的终止
4.4.1 stop()——在java多线程中已经废弃
- stop()方法会导致释放锁的不良后果,数据不完整。
比如一个上锁了得方法:threadA线程拥有了监视器,这些监视器负责保护某些临界资源,比如说银行的转账的金额。当正在转账过程中,main线程调用 threadA.stop()方法或者this.stop()。结果导致监视器被释放,其保护的资源(转账金额)很可能出现不一致性。比如,A账户减少了100,而B账户却没有增加100,没有保证数据原子性 - 当线程调用stop()方法时,会立刻终止线程的所有操作,并抛出ThreadDeath异常,通常不需要捕捉
4.4.2 interrupt()
- 设置一个共享变量的值 true
- 唤醒处于阻塞状态下的线程
我的理解:
interrupt()方法在线程不阻塞的时候不会中断线程,在线程阻塞的时候,会使线程抛出interruptException异常来达到中断线程的效果。
相关方法:
方法 | 描述 |
---|---|
interrupt() | 中断线程,将会设置该线程的中断状态位,即设置为true |
isInterrupted() | 判断某个线程是否已被发送过中断请求 |
interrupted() | 判断某个线程是否已被发送过中断请求,并且将中断状态重新置为false(有一说一,这个方法名取的真离谱) |
interrupt简述
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。 更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException。
- 线程处于阻塞状态: 当线程使用了sleep,wait,socket中的receiver,accept等方法时。
此时调用线程的interrupt方法,会抛出interruptException。 抛出异常时,程序被阻塞中断标志复位,变为false,也就是调用isInterrupted()会返回false。
阻塞中的方法抛出异常,通过代码捕获,然后break跳出循环状态,才能正常结束run方法。 - 线程是未阻塞状态, 使用isinterrupt()方法判断线程的中断标志来退出循环。
使用interrupt方法,会把中断标志设置为true。
和使用自定义标志来控制循环是一样的。
注意:调用sleep,wait等方法时,代码提示会抛出interruptException。
t1.isInterrupted():返回当前程序中断标志位是否中断。——实例方法
t1.interrupt():中断。——实例方法
Thread.interrupted():复位。——静态方法
demo如下:
public class InterruptDemo02 implements Runnable{
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){ //false
try {
TimeUnit.SECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
//可以不做处理,
//继续中断 ->
Thread.currentThread().interrupt(); //再次中断
//抛出异常。。
}
}
System.out.println("processor End");
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new InterruptDemo02());
t1.start();
Thread.sleep(1000);
t1.interrupt(); //有作用 true
Thread.interrupted() ;//复位
}
}