一、进程与线程
1、进程和线程分别是什么?
进程:进程是计算机中和程序关于数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
线程:进程的子集,它是进程内部的一个独立的执行单元,一个进程可以同时并发执行多个线程。
2、进程和线程的区别
进程:有独立的内存空间,进程中的数据存放空间是独立的(堆空间和栈空间都独立),进程中至少有一个线程。
线程:推空间是共享的,而栈空间是独立的,纯种消耗的资源比进程小的多。
3、注意
① 因为一个进程中的多个线程是并发运行的,那么从微观上看它是有先后顺序的,哪个线程应该执行完全取决于CPU的调度,程序员是干涉不了的,这也决定了多线程运行的随机性;
② Java程序的进程至少包含两个线程,一个是主线程也就是main()方法执行的线程,一个是垃圾回收机制线程。
二、进程的三态和五态模型
1、进程的五态模型
在五态模型中,进程分为新建态(new)、就绪态(ready)、运行态(running)、阻塞态(waiting)、终止态(terminated)。
新建态:进程在创建时需要申请一个空白进程管理块,向其中填写控制和管理进程的信息,完成资源分配;
就绪态:进程已经准备好,已经分配到所需资源,只要分配到CPU就能够立刻运行;
运行态:进程处于就绪状态被调度后,进入运行态;
阻塞态:正在执行的进程由于某些某些事件(如I/O调用)而无法运行,进程受到阻塞;
终止态:进程结束,或出现错误,或被系统终止,进入终止状态。
它们的关系如下图:
2、进程的三种状态之间的转化
进程的三种状态之间的转化指的是就绪态、运行态和阻塞态之间的转化。从理论上分析有4可能的情况,如下:
① 就绪态 》》运行态:其他进程时间片用完,CPU调度选中一个就绪进程执行;
② 运行态 》》就绪态:分配给每个进程的时间片是有限的,运行时间片到了或出现更高优先权进程就进入到就绪态;
③ 运行态 》》阻塞态:正在执行的进程因发生某种等待事件而无法执行,如发生了I/O请求,此时由运行态转换到阻塞态;
④ 阻塞态 》》 就绪态:进程所等待的事件已经发生,进程进入就绪队列。
它们之间的转化图如下:
三、JVM中的线程状态
1、JVM中线程的六种状态
在Thread类中,从JDK1.5开始定义了一个枚举类State,枚举了线程的六种状态。通过Thread的getState()方法可以获取线程的状态。
① 新建状态(NEW):一个尚未启动的线程所处的状态;
② 可运行状态(RUNNABLE):可运行线程的线程状态,可能正在运行,也可能在等待处理器资源;
③ 阻塞状态之锁阻塞(BLOCKED):被阻塞等待监视器锁定的线程所处的状态(即当一个线程试图获取锁,但锁此时被其他线程持有,该线程进入BLOCKED状态,当线程拿到锁则进入RUNNABLE状态);
④ 阻塞状态之无限等待(WAITING):未指定等待时间的线程所处的状态(调用Object.join()或Object.wait()方法时进入此状态,一个线程处于该状态时,只能被另一个线程唤醒,而不能自己主动唤醒,另一个线程调用notify()或notifyAll()来唤醒该线程);
⑤ 阻塞状态之定时等待(TIMED_WAITING):指定等待时间的线程所处的状态(调用Thread.sleep(long)或Object.join(long)或Object.wait(long)方法时进入此状态,直到时间超时或收到唤醒通知,注意wait(0)时是可以自己苏醒的,比如当Thread结束时就会自动苏醒);
⑥ 终止状态:已经执行完成的线程状态。
关于wait()和join()的使用区别可以参考:join和wait
2、操作系统进程的状态与JVM中的线程状态区分
JVM中的线程状态跟操作系统进程的五态模型是有区别的,Java线程在Windows平台和Linux平台上的实现方式,是内核线程的实现方式,这样的方式实现的线程,是直接由操作系统内核支持的,内核通过操纵调度器来实现线程调度。也就是说,Java的线程跟操作系统的内核之间存在映射关系,这种映射可以是一对一映射,也可以是一对多或多对多映射。所以说,Java中任一给定时间的线程状态是不反映任何操作系统进程状态的虚拟机状态。
进程与线程的区分图如下:
3、JVM中线程状态的转化
线程状态的转化图如下:
① NEW 》》RUNNABLE:当一个新创建的线程调用start()方法时,该线程进入RUNNABLE状态;
public class ThreadStateTest {
public static void main(String[] args) throws Exception{
ThreadStateTest test = new ThreadStateTest();
test.newToRunnable();
}
//状态NEW到RUNNABLE的转化
private void newToRunnable(){
Thread thread = new Thread(new MyThread(),"张三");
System.out.println("当前线程状态为:" + thread.getState());
thread.start();
System.out.println("当前线程状态为:" + thread.getState());
}
}
//实现了Runnable接口的自定义线程
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("自定义线程正在执行中。。。");
}
}
执行结果如下:
当前线程状态为:NEW
当前线程状态为:RUNNABLE
自定义线程正在执行中。。。
② RUNNABLE 》》TERMINATED:当一个线程执行完毕或被终止或出现异常将会进入TERMINATED状态;
public static void main(String[] args) throws Exception{
ThreadStateTest test = new ThreadStateTest();
//test.newToRunnable();
test.runnableToTerminated();
}
//状态RUNNABLE到TERMINATED的转化
private void runnableToTerminated()throws Exception{
Thread thread = new Thread(new MyThread(),"张三");
thread.start();
System.out.println("当前线程状态为:" + thread.getState());
//让主线程休眠5秒钟,不去跟新建的线程抢CPU运行时
Thread.sleep(5000);
System.out.println("当前线程状态为:" + thread.getState());
}
执行结果如下:
当前线程状态为:RUNNABLE
自定义线程正在执行中。。。
当前线程状态为:TERMINATED
③ RUNNABLE 》》TIMED_WAITING 》》RUNNABLE:RUNNABLE状态的线程调用Thread.sleep(long)或Object.join(long)或Object.wait(long)方法时进入此状态,直到超时或被唤醒;
④ RUNNABLE 》》BLOCKED 》》RUNNABLE:当前线程试图获取锁,但有一个线程持有锁,当前线程则进入BLOCKED状态,获取锁后进入RUNNABLE状态;
综合示例如下:
public static void main(String[] args) throws Exception{
ThreadState test = new ThreadState();
//test.newToRunnable();
//test.runnableToTerminated();
test.runnableToBlockedOrTimedWaiting();
}
//状态RUNNABLE到BLOCKED和TIMED_WAITING的状态转化
private void runnableToBlockedOrTimedWaiting()throws Exception{
Object obj = new Object();
Thread thread1 = new MyThreadSyn1("张三",obj);
Thread thread2 = new MyThreadSyn2("李四",obj);
thread1.start();
thread2.start();
System.out.println("1."+ thread1.getName() + thread1.getState());
System.out.println("2."+ thread2.getName() + thread2.getState());
//主线程休眠2秒钟,让业务线程执行,当执行到休眠5秒的时间观察线程状态变化
Thread.sleep(2000);
System.out.println("5." + thread1.getName() + thread1.getState());
System.out.println("6." + thread2.getName() + thread2.getState());
}
//自定义线程1,业务中包含同步锁
class MyThreadSyn1 extends Thread{
private Object obj;
//构造方法除了传一个名字之外,还要传两个线程共同使用的锁对象
MyThreadSyn1(String name, Object obj){
super(name);
this.obj = obj;
}
@Override
public void run(){
synchronized (obj){
System.out.println("3." + Thread.currentThread().getName()+"线程开始执行业务逻辑");
System.out.println("4." + Thread.currentThread().getName() +
Thread.currentThread().getState());
try {
//当前线程休眠5秒钟
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("7." + Thread.currentThread().getName()+"线程的业务逻辑执行完毕");
}
}
}
//自定义线程2,业务中包含同步锁
class MyThreadSyn2 extends Thread{
private Object obj;
//构造方法除了传一个名字之外,还要传两个线程共同使用的锁对象
MyThreadSyn2(String name, Object obj){
super(name);
this.obj = obj;
}
@Override
public void run(){
synchronized (obj){
System.out.println("8." + Thread.currentThread().getName()+"线程开始执行业务逻辑");
System.out.println("9." + Thread.currentThread().getName() +
Thread.currentThread().getState());
try {
//当前线程休眠5秒钟
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("10." + Thread.currentThread().getName()+"线程的业务逻辑执行完毕");
}
}
}
为了方便查看执行顺序,所以标上了执行序号,执行结果如下:
1.张三RUNNABLE
2.李四RUNNABLE
3.张三线程开始执行业务逻辑
4.张三RUNNABLE
//说明:在这里张三线程开始休眠所以进入了TIMED_WAITING状态,而李四线程试图获取锁,但锁此时被线程张三
//占用,所以李四就获取不到锁从而进入BLOCKED状态,当张三线程休眠结束并执行完代码之后,李四获取到锁
//并执行线程李四的业务逻辑,所以李四此时就处于RUNNABLE状态
5.张三TIMED_WAITING
6.李四BLOCKED
7.张三线程的业务逻辑执行完毕
8.李四线程开始执行业务逻辑
9.李四RUNNABLE
10.李四线程的业务逻辑执行完毕
⑤ RUNNABLE 》》WAITING 》》RUNNABLE:当线程调用wait或join方法时进入无限等待,另一个线程调用notify方法来唤醒该线程,然后进入RUNNABLE状态。
综合示例如下:
public static void main(String[] args) throws Exception{
ThreadState test = new ThreadState();
//test.newToRunnable();
//test.runnableToTerminated();
//test.runnableToBlockedOrTimedWaiting();
test.runnableToWaiting();
}
//状态RUNNABLE到WAITING的状态转化
private void runnableToWaiting() throws Exception{
//无限等待使用Object中的wait方法完成,直到有其他线程进行唤醒操作
//两个线程执行不同的操作,关联的两个线程使用同样的锁,只能使用锁对象的wait()和notify()
final Object obj = new Object();
Thread t1 = new Thread("张三"){
@Override
public void run() {
synchronized (obj){
try {
System.out.println("1.调用wait方法,线程" +
Thread.currentThread().getName() + "开始进入无限等待");
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("5.线程已经被唤醒,无限等待结束");
//张三获取到锁进入RUNNABLE状态
System.out.println("6." + Thread.currentThread().getName() +
Thread.currentThread().getState());
}
}
};
t1.start();
Thread.sleep(3000);
System.out.println("2." + t1.getName() + t1.getState());
Thread.sleep(3000);
new Thread(){
@Override
public void run() {
synchronized (obj){
System.out.println("3.开始唤醒线程张三");
//将唤醒所有与锁对象关联的线程
obj.notify();
//此时线程张三被唤醒但没获取到锁,所以进入BLOCKED状态
System.out.println("4." + t1.getName() + t1.getState());
}
}
}.start();
Thread.sleep(3000);
System.out.println("7." + t1.getName() + t1.getState());
}
执行结果如下:
1.调用wait方法,线程张三开始进入无限等待
2.张三WAITING
3.开始唤醒线程张三
4.张三BLOCKED
5.线程已经被唤醒,无限等待结束
6.张三RUNNABLE
7.张三TERMINATED
四、演示多线程的其他例子
1、创建多线程的两种方式
(1) 继承Thread类
/**
* author Alex
* date 2019/3/1
* description 第一种多线程的创建方法:继承Thread类
*/
public class ExtendsThread {
public static void main(String[] args) {
MyThread1 thread1 = new MyThread1("张三");
MyThread2 thread2 = new MyThread2("李四");
//在没有调用start()方法之前thread1和thread2都处于新建状态
thread1.start();
thread2.start();
}
}
//线程张三
class MyThread1 extends Thread{
MyThread1(String name){
super(name);
}
@Override
public void run() {
for (int i = 0;i< 5;i++){
System.out.println(this.getName() + i);
}
}
}
//线程李四
class MyThread2 extends Thread{
MyThread2(String name){
super(name);
}
@Override
public void run() {
for (int i = 0;i< 5;i++){
System.out.println(this.getName() + i);
}
}
}
执行结果如下:
张三0
张三1
李四0
张三2
李四1
张三3
李四2
张三4
李四3
李四4
请注意:多线程在执行时可能会出现一个线程连续执行而不是交替执行的状况,这可能是由于当前线程的执行处于同一个CPU时间片内所导致的,如果出现这种状况,可以增加循环的次数来验证,一般循环达到1000次,就可以明显的看到交替执行了。如果还不行,请检查代码是否写的有问题。
现在我们在main()方法中也加入一个循环看看一个主线程和两个自定义线程的执行状况,添加的代码如下所示:
public class ExtendsThread {
public static void main(String[] args) {
MyThread1 thread1 = new MyThread1("张三");
MyThread2 thread2 = new MyThread2("李四");
//在没有调用start()方法之前thread1和thread2都处于新建状态
thread1.start();
thread2.start();
for(int i = 0;i<500;i++){
System.out.println("主线程" + i);
}
}
}
请注意:main()方法中的循环如果放在下面两个线程创建之前,那么无论执行多少次都无法看到主线程与下面两个线程的交替执行的。因为在下面两个线程还没有创建之前,就没有其他线程跟主线程抢占CPU的执行时间,那么在主线程中的代码就会按顺序从上到下执行,只有当下面两个线程创建之后加入了线程队列,CPU才会根据调度算法随机给三个线程分配CPU时间片,这时才会出现交替执行的状态。
执行结果如下:
//由于循环次数太少无法出现交替执行,这里将循环次数增加到500次
主线程486
李四15
张三25
李四16
主线程487
李四17
张三26
李四18
主线程488
李四19
张三27
(2) 实现Runnable接口
package many.thread;
/**
* author Alex
* date 2019/3/1
* description 第二种多线程的创建方法:实现Runnable接口
*/
public class ImplementsRunnable {
public static void main(String[] args) {
//创建任务线程
MyThread3 mt = new MyThread3();
//创建执行任务的线程
Thread thread1 = new Thread(mt, "张三");
Thread thread2 = new Thread(mt, "李四");
thread1.start();
thread2.start();
}
}
//实现Runnable接口的线程
class MyThread3 implements Runnable{
@Override
public void run() {
for(int i = 0;i<500;i++){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
执行结果如下:
李四121
张三147
李四122
张三148
李四123
张三149
李四124
李四125
张三150
李四126
张三151
2、使用同步锁解决线程安全的问题
/**
* author Alex
* date 2019/3/1
* description 使用同步锁解决线程安全的问题:模拟火车网上售票业务
*/
public class SynBlockThread {
public static void main(String[] args) {
//创建卖票任务对象
Ticket ticket = new Ticket();
//三个售票窗口
Thread thread1 = new Thread(ticket,"窗口一");
Thread thread2 = new Thread(ticket,"窗口二");
Thread thread3 = new Thread(ticket,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
//业务线程
class Ticket implements Runnable{
//火车票的总数
private int num = 100;
//锁对象
private final Object obj = new Object();
@Override
public void run() {
while (true){//无限循环代表售票业务一直存在
synchronized (obj){//添加同步锁,使用obj对象作为对象锁
if(num > 0){//只有票的总数大于0才能进行售票
try {
//休眠50毫秒,模拟出票时间
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前总票数:" + num-- + "," + Thread.currentThread().getName() +
"开始售票");
}
}
}
}
}
执行结果如下:
当前总票数:89,窗口一开始售票
当前总票数:88,窗口一开始售票
当前总票数:87,窗口三开始售票
当前总票数:86,窗口三开始售票
当前总票数:85,窗口三开始售票
当前总票数:84,窗口三开始售票
当前总票数:83,窗口二开始售票
当前总票数:82,窗口二开始售票
......