多线程
什么是进程、线程、携程
进程
进程是相对于 程序而言,程序是一堆静止的文件, 而当程序运行起来以后 就是一个进程,是动态的。进程是系统资源分配的最小单位, 进程通过消息队列,共享内存等机制实现进程间通信。
比如操作系统多任务,一个操作系统可以同时运行多个进程,多个独立的应用程序
一个进程包含多个线程,比如音乐播放器:可以一边放歌曲(线程),一边播放歌词(线程)。
线程
线程是由进程创建的,是CPU调度的最小单位
指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
同时线程的调度是不受控制的,由操作系统调度,再Java中则由JVM统一调度
携程
协程是属于线程的一种顺序处理,在任何给定时间只有一个协程在执行。这个是可以由程序员进行调度
线程生命周期
先上一张经典图
- 新建(new):线程对象被创建后就进入了新建状态。如:Thread thread = new Thread();
- 就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其他线程调用了该对象的start()方法,从而启动该线程。如:thread.start(); 处于就绪状态的线程随时可能被CPU调度执行。
- 运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:
- 等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
- 通过调用线程的sleep()睡眠、yeild()让步、join()合并或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程执行完了或因异常退出了run()方法,该线程结束生命周期。
线程创建(五种方法)
1.继承Thread
public class UserThread extends Thread{
public void run() {
System.out.println("线程的run....");
}
}
public class Test {
public static void main(String[] args) {
//创建线程,默认线程名Thread-0,依此类推。
//Thread.currentThread().getName()通过此方法获得当前线程名
UserThread ut = new UserThread();
ut.start();//进入就绪状态
}
}
通过继承Thread父类,重写run方法来创建线程
启动线程使用start(),此时进入就绪状态,如果不存在其余啥操作就会直接执行
2.实现Runnable接口
public class UserRunn implements Runnable{
public void run() {
// TODO Auto-generated method stub
System.out.println("run方法是由线程:"+Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
//线程接口类对象
UserRunn ur = new UserRunn();
//Runnable接口是Thread的父类
Thread u = new Thread(ur);
u.start();
}
}
因为Runnable是Thread的父类,所以此方法主要通过继承Runnable接口,创建对象,再通过这个创建Thread的对象,实现线程创建。
继承Thread 类和实现Runnable 接口的区别
这时有人问了(假装有人捧场~~):啊,那既然Runnable是Thread的父类了,为啥我们不直接继承Thread呀?这不是多此一举????
其实不然,大家可以想想类和接口的区别,类时单继承,而接口时多继承,也就是说如果使用第一个继承Thread方法,就无法再继承别的方法,不经意间就会带来很多烦恼而通过实现Runnable接口则不同,多继承的特点格外让人精神愉悦
同时,如果一个类继承Thread,则不适合资源共享,而实现Runable接口的话,则很容易的实现资源共享。
[具体关于资源共享的举例可以参考一下这篇]
3.定时线程
public class UserTask extends TimerTask{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+",run,"+new Date());
}
}
public class Test {
public static void main(String[] args) {
//通过定时器类去调度
Timer t = new Timer();
//调度 UserTask() 延迟1秒 间隔3秒
//t.schedule(new UserTask(), 1000, 3000);
//在指定的日期时间定时执行
String strTime="2022-03-20 23:52:00";
//字符串的日期转换成Date类型
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date =null;
try {
date = dateFormat.parse(strTime);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t.schedule(new UserTask(), date, 3000);
}
}
这个方法主要时通过继承定时器类TimerTask通过固定时间或者等待相应时间,创建线程。他是一个继承Runnable接口的抽象类,但是他有着特殊的定时功能,也可以通过设定时间间隔,重复创建线程进行同样的工作。
4.实现Callable接口
此段后期更新哦~~
5.线程池
来八个字体现一下他的功能:线程不死 反复使用
自定义线程池
首先咱们说说自定义线程池,这也是线程池的底层逻辑,这也是本小白的第一次尝试
首先配置线程池大小,池的话这边用的数组实现,其实更应该用集合(但是还没学到😢),毕竟数组长度固定,
//线程池的大小配置
private int size;
//池:数据容器 数组
Runnable[] r = null;
然后实现构造函数,然后实例化刚刚的数组,当然存的都是线程对象
public ThreadPool(int size)
{
//这边通过调用私有变量,回头调用方法的时候可以自己设定线程池大小
this.size =size;
//实例化一维数组 实例化了
this.r = new Runnable[this.size];
for(int i=0;i<r.length;i++)
{
//这边调用WorkThread类创建线程对象
WorkThread w = new WorkThread();
this.r[i]=w;
w.start();
}
}
关于WorkThread类通过继承Thread方法创建对象,用flag判断线程执行状态,同时获取前端输入的线程任务名称
//判断线程是否执行 true:线程有任务 false:线程没有任务
private boolean flag;
public boolean isFlag() {
return flag;
}
//获取线程对于任务名称名称
private String taskname;
public void setTaskname(String taskname) {
this.taskname = taskname;
}
接下来就是最重要的通过同步锁机制实现线程池中的线程无限使用
刚开始,flag=false时进入else直接.wait()进入等待阻塞状态
public synchronized void run()
{
while(true)
{
if(flag)
{
System.out.println(Thread.currentThread().getName()+"从线程池中出来,正在执行任务,任务名称是:"+this.taskname);
try {
//一个线程完成一个任务需要20秒
Thread.sleep(20*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//完成任务的线程 回到线程
this.setFlag(false);
}
else
{
System.out.println(Thread.currentThread().getName()+"进入到线程池,线程等待任务的分配");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
同时需要设定一个setFlag方法将以有任务的线程对象通过.notify()方法唤醒
public synchronized void setFlag(boolean flag) {
this.flag = flag;
if(this.flag){
this.notify();
}
}
当然咯,线程池那边也需要相对应的方法执行,直接上代码吧
public void execTask(String taskName)
{
for(int i =0;i<r.length;i++)
{
WorkThread w = (WorkThread)r[i];
System.out.println("线程池中的线程现在的状态(有没有任务在执行):"+w.isFlag());
if(!w.isFlag())
{
w.setTaskname(taskName);
w.setFlag(true);
//有一个线程执行任务,一个线程一个任务
break;
}
}
}
Q.E.D.