多线程

什么是进程、线程、携程

进程

进程是相对于 程序而言,程序是一堆静止的文件, 而当程序运行起来以后 就是一个进程,是动态的。进程是系统资源分配的最小单位, 进程通过消息队列,共享内存等机制实现进程间通信。
比如操作系统多任务,一个操作系统可以同时运行多个进程,多个独立的应用程序
一个进程包含多个线程,比如音乐播放器:可以一边放歌曲(线程),一边播放歌词(线程)。

线程

线程是由进程创建的,是CPU调度的最小单位
指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
同时线程的调度是不受控制的,由操作系统调度,再Java中则由JVM统一调度

携程

协程是属于线程的一种顺序处理,在任何给定时间只有一个协程在执行。这个是可以由程序员进行调度

线程生命周期

先上一张经典图
2258ba58b5e31f9daaca44d7cf251055.png

  1. 新建(new):线程对象被创建后就进入了新建状态。如:Thread thread = new Thread();
  2. 就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其他线程调用了该对象的start()方法,从而启动该线程。如:thread.start(); 处于就绪状态的线程随时可能被CPU调度执行。
  3. 运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:
    1. 等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。
    2. 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
    3. 通过调用线程的sleep()睡眠、yeild()让步、join()合并或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
  5. 死亡状态(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.