进程
概念
进程就是正在运行的程序,它代表了程序所占用的内存区域
特点
- 独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间 - 动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
- 并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
并行与并发
- 并发:多个进程抢占CPU
- 并行:多个进程都有自己的CPU进行处理,无抢占现象。
线程
概念
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程
多线程扩展了多进程的概念,使的同一个进程可以同时并发处理多个任务,每个线程拥有自己独立的内存空间,多线程之间也存在共享数据。
同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
进程与线程
一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)
多线程特性
- 随机性:
线程的随机性指的是同一时刻,只有一个程序在执行
宏观:多个进程/线程同时运行
微观:一个CPU同一时刻只能处理一个任务 - 分时调度:
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程。将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
三态模型
- 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
- 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
- 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
就绪 → 执行:为就绪线程分配CPU即可变为执行状态”
执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行
五态模型
- 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
- 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
PCB(Process Control Block):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程
线程生命周期
- 新建状态(New) : 当线程对象创建后就进入了新建状态.如:
Thread t = new MyThread()
; - 就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()
此线程立即就会执行 - 运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态a
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态 - 阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:- 等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
- 其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
多线程实现方式
继承Thread()方法
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
启动线程的唯一方法就是通过Thread类的start()实例方法。
start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run()。
这种方式实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run()方法。
模拟开启多个线程,每个线程调用run()方法。
常用方法:
构造方法
方法 | 作用 |
---|---|
Thread(Runnable target) |
分配一个新的 Thread 对象。 |
Thread(Runnable target, String name) |
分配一个新的 Thread 对象。 |
Thread(String name) |
分配一个新的 Thread 对象。 |
Thread() |
分配一个新的 Thread 对象。 |
普通方法
返回值类型 | 方法名 |
---|---|
static Thread |
currentThread() 返回对当前正在执行的线程对象的引用。 |
long getId() |
返回该线程的标识 |
String getName() |
返回该线程的名称 |
void run() |
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法 |
static void sleep(long millions) |
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
void start() |
使该线程开始执行:Java虚拟机调用该线程的run() |
示例代码
package cn.tedu.thread;
/**
* 多线程实现方式
* 线程随机性:多个线程对象执行效果不可控,由CPU调度处理,结果具有随机性
* 哪个时间片执行哪个线程以及时间片有多长,只能由CPU控制,无法人为调控
*/
public class TestThread {
public static void main(String[] args) {
MyThread m = new MyThread();//对应线程为新建状态
MyThread m1 = new MyThread();//对应线程为新建状态
MyThread m2 = new MyThread();//对应线程为新建状态
MyThread m3 = new MyThread();//对应线程为新建状态
MyThread m4 = new MyThread("马钊");//对应线程为新建状态
/*
使用run()方法,普通方法的调用
没有多线程的效果
m.run();
m1.run();
*/
m.start();//start()方法会把线程加入就绪队列,以多线程的方式调用
m1.start();
m2.start();
m3.start();
m4.start();
}
}
class MyThread extends Thread{
/*
线程中的业务必须写在run()里,不执行父类的run(),执行自己的业务
*/
@Override
public void run() {
/*
打印10次当前正在执行的线程名称
getName()可以获取当前正在执行的线程名称
由父类继承而来,可以直接调用
*/
for (int i = 0;i < 10;i++){
System.out.println("第" + (i+1) + "个" + getName());
}
}
public MyThread() {
}
public MyThread(String name) {
/*
子类对象构造方法触发父类构造方法来给线程起名
*/
super(name);
}
}
实现Runnable接口
概述
如果类已经extends,另一个类,就无法多继承,此时,此时可以实现一个Runnable接口
常用方法
void run()
使用实现接口Runnable的对象创建线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法
示例代码
package cn.tedu.thread;
/**
* 多线程实现方式
*/
public class TestRunnable {
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
//将接口实现类对象与Thread建立关系
Thread thread = new Thread(m,"马钊");
Thread thread1 = new Thread(m,"桂宏宇");
Thread thread2 = new Thread(m,"雨来");
Thread thread3 = new Thread(m,"泡泡");
/*
m.run()不可用,不是多线程的启动方式
m.start(),报错,Runnable与实现类都没有start方法
*/
thread.start();
thread1.start();
thread2.start();
thread3.start();
}
}
//implements Runnable实现多线程
class MyRunnable implements Runnable{
//将具体业务写入run(),实现Runnable接口中的run()
@Override
public void run() {
/*
获取当前正在执行的线程对象,静态方法可以直接使用Thread调用
Thread.currentThread()
获取当前正在执行的线程对象,静态方法可以被Thread类名直接调用
线程对象, getName()获取当前正在执行的线程对象的名称
*/
for (int i = 0;i < 10;i++){
System.out.println("第" + i + "次" + Thread.currentThread().getName());
}
}
}
两种方式比较
继承Thread类
优点: 编写简单,如果需要访问当前线程,无需使用Thread.currentThread()
方法,直接使用this即可获得当前线程
缺点: 自定义的线程类已继承了Thread类,所以后续无法再继承其他的类
实现Runnable接口
优点: 自定义的线程类只是实现了Runnable接口或Callable接口,后续还可以继承其他类,在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、还有数据分开(解耦),形成清晰的模型,较好地体现了面向对象的思想
缺点: 编程稍微复杂,如想访问当前线程,则需使用Thread.currentThread()
方法
售票案例
继承Thread
package cn.tedu.thread;
/**
* 多线程实现方式
* 线程随机性:多个线程对象执行效果不可控,由CPU调度处理,结果具有随机性
* 哪个时间片执行哪个线程以及时间片有多长,只能由CPU控制,无法人为调控
*/
public class TestThread {
public static void main(String[] args) {
MyThread m = new MyThread();//对应线程为新建状态
MyThread m1 = new MyThread();//对应线程为新建状态
MyThread m2 = new MyThread();//对应线程为新建状态
MyThread m3 = new MyThread();//对应线程为新建状态
MyThread m4 = new MyThread("马钊");//对应线程为新建状态
/*
使用run()方法,普通方法的调用
没有多线程的效果
m.run();
m1.run();
*/
m.start();//start()方法会把线程加入就绪队列,以多线程的方式调用
m1.start();
m2.start();
m3.start();
m4.start();
}
}
class MyThread extends Thread{
/*
线程中的业务必须写在run()里,不执行父类的run(),执行自己的业务
*/
@Override
public void run() {
/*
打印10次当前正在执行的线程名称
getName()可以获取当前正在执行的线程名称
由父类继承而来,可以直接调用
*/
for (int i = 0;i < 10;i++){
System.out.println("第" + (i+1) + "个" + getName());
}
}
public MyThread() {
}
public MyThread(String name) {
/*
子类对象构造方法触发父类构造方法来给线程起名
*/
super(name);
}
}
实现Runnable接口
package cn.tedu.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 多线程实现方式
*/
public class TestRunnable {
public static void main(String[] args) {
MyRunnable target = new MyRunnable();
/*//将接口实现类对象与Thread建立关系
Thread thread = new Thread(target"马钊");
Thread thread1 = new Thread(target,"桂宏宇");
Thread thread2 = new Thread(target,"雨来");
Thread thread3 = new Thread(target,"泡泡");
*//*
m.run()不可用,不是多线程的启动方式
m.start(),报错,Runnable与实现类都没有start方法
*//*
thread.start();
thread1.start();
thread2.start();
thread3.start();*/
/*
创建线程池
Executors
newFixedThreadPool(线程数)方法创建指定线程数的线程池
线程池类型为:ExecutorService
*/
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0;i < 5;i++){
//使用池对象完成任务,人任务参数为target
pool.execute(target);
}
}
}
//implements Runnable实现多线程
class MyRunnable implements Runnable{
//将具体业务写入run(),实现Runnable接口中的run()
@Override
public void run() {
/*
获取当前正在执行的线程对象,静态方法可以直接使用Thread调用
Thread.currentThread()
获取当前正在执行的线程对象,静态方法可以被Thread类名直接调用
线程对象, getName()获取当前正在执行的线程对象的名称
*/
for (int i = 0;i < 10;i++){
System.out.println("第" + i + "次" + Thread.currentThread().getName());
}
}
}
判断程序有无线程安全问题
在多线程数据中+有共享数据+多条语句操作共享数据