详述Java中的线程1——线程与进程

目录

 

一、什么是进程?

二、什么是线程?

三、线程与进程的关系

四、Java中多线程的实现

创建线程:

开启线程:

线程生命周期:

线程池:


一、什么是进程?

程序(Program)是为实现特定目标或解决特定问题而用计算机语言(比如Java语言)编写的命令序列的集合。

进程process指一个程序的一次执行过程

Java中的进程,听起来很宽泛的一个概念,及我们俗称的进度,Java中,程序无非运行和停止两种状态,进程即是我们程序的运行状。想像这样一个场景:我是一个公司职员,我的领导给我安排了一项事务,他会说:“你先去.......,然后.........,在做........的时候把.......给完成............”,这一系列的基本口令构成了这一项事务,我开始着手,一段时间后,领导会来问:“事情进行到哪里了?”,“进行到哪里”提问的对象即为“进程”,在整个事务未完成的情况下,可以理解为进展。事务完成,则进程结束。

二、什么是线程?

线程(thread)又称为轻量级进程,线程是一个程序中实现单一功能的一个指令序列,是一个程序的单个执行流,存在于进程中,是一个进程的一部分。

线程即口令,即上面场景中领导给我的一系列口令。

三、线程与进程的关系

一个进程可以包含多个线程,而一个线程必须在一个进程之内运行;同一进程中的多个线程之间采用抢占式独立运行;进程结束,则线程跟着结束,如果一个进程没有可执行的线程,进程也结束;

回到上面场景中,领导交给我的事务,最起码有一条口令,否则领导的这个事务就不会产生;而我在执行事务中的这些口令时提高效率,一定不会一条一条执行,肯定是动态的,随机应变的执行,万一执行某条口令时出了问题,不能影响领导交给我的这个事务的完成,所以要暂时切换到另一条口令;但万一领导告诉我,这个是务因为某些原因要停止,那么我就要停止所有口令的执行;如果领导交给我的事务中所有口令我都执行完了,那么好,这个事务我完成了,也就停止了。

四、Java中多线程的实现

创建线程:

Java中Thread类即为线程,想要创建线程有两种方式。

(1)继承java.lang.Thread,重写run方法;

class CountThread extends Thread {//线程1

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			System.out.println("计数1: " + i);//这里我做一个简单的循环输出
		}
	}
}

(2)实现java.lang.Runnable接口,实现run抽象方法。

class TimeThread implements Runnable {//线程2

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			System.out.println("计数2: " + i);
		}
	}

}

开启线程:

在主类中开启线程:

package Mars;

public class Test {
	public static void main(String[] args) {
		
		new CountThread().start();//继承自Thread类的线程类对象可通过直接调用start()方法来开启线程
		
		new Thread(new TimeThread()).start();//Runnable接口的实现类在开启线程时要通过Thread类有参构造方法创建Thread类对象,实现类创建的对象作为参数传入

	}
}

运行:

由于输出数据较少,所以这里无法观察到,线程之间的抢占式的运行模式,不妨给两个线程的每次输出之间设置时间间隔:

修改线程1:

class CountThread extends Thread {//线程1

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			try {
				sleep(1000);//sleep()是Thread类中的静态方法,所以子类继承后可以直接调用,会使得线程的运行进入暂时地等待的状态,用参数设置等待时间,这里设置为1000毫秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("计数1: " + i);
		}
	}
}

 修改线程2:

class TimeThread implements Runnable {//线程2

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			try {
				Thread.sleep(1000);//该类不是Thread类的子类,所以需要用Thread类名调用
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("计数2: " + i);
		}
	}

}

运行:

 

由图可知,在进程开始时,线程1先抢占内存,先进入休眠,先输出,所以穿插输出且每次都为线程1先输出。

线程生命周期:

因为线程是一个一个独立的口令,所以在程序进程中也有一定生命周期:

Java中,线程有5种不同状态,分别是:新建(New就绪(Runable运行(Running阻塞(Blocked死亡(Dead

线程生命周期期间各种状态与转换原因如图

当我们创建的线程执行start()方法时,实际上线程只是进入了就绪状态,随时等待运行,因为前面说到,线程运行的方式抢占式运行,所以一旦有线程先抢占CPU开始运行,那么其他线程都要进入就绪状态。

阻塞也会使得一个线程进入就绪状态,示例中的sleep()方法就是一个阻塞线程的方法。即使该线程已率先抢占CPU运行,一旦被阻塞,其他线程就直接抢占CPU使用权。所以,在死亡之前,线程始终在阻塞,运行,就绪三个状态之间转换。所以,当start()方法执行时,线程只是进入了就绪状态,运行与否受到是否得到CPU的使用权来决定。


在进行下面的叙述之前,先分析一下上方整个代码的执行过程: 

package Mars;

public class Test {
	public static void main(String[] args) {

		new CountThread().start();// 继承自Thread类的线程类对象可通过直接调用start()方法来开启线程

		new Thread(new TimeThread()).start();// Runnable接口的实现类在开启线程时要通过Thread类有参构造方法创建Thread类对象,实现类创建的对象作为参数传入

	}
}

class CountThread extends Thread {// 线程1

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			try {
				sleep(1000);// sleep()是Thread类中的静态方法,所以子类继承后可以直接调用,会使得线程的运行进入暂时地等待的状态,用参数设置等待时间,这里设置为1000毫秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("计数1: " + i);
		}
	}
}

class TimeThread implements Runnable {// 线程2

	@Override
	public void run() {
		for (int i = 0; i < 15; i++) {
			try {
				Thread.sleep(1000);// 该类不是Thread类的子类,所以需要用Thread类名调用
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("计数2: " + i);
		}
	}

}

首先,在我们没创建新线程之前,整个程序的运作就只有以main方法为参照的主线程,之所以称之为主线程,是因为main方法在Java程序中是执行标杆。那么我们退一步看,当我们新创建了两个线程后,实际上这个类,每个类都只是一个线程而已,都需要抢占CPU使用权。我管这叫线程平等性,这些享有同样的权利,即使是main方法也一样。 这个观点在我的下篇博客中显得十分重要。

上方代码中main方法比较简单,只是开启了两个线程,这两条语句执行后,主线程也就终止了,但是,其他两个线程还没有终止,这就体现了线程平等性而当我们某个线程出现运行时异常其他线程也不受影响,这是线程的各自独立性。

线程池:

我们知道,计算机的运行内存是有限的,假设我们我们在一个程序进程中一次开启了多个进程,是相当占用运行内存的,势必会为计算机带来负担。这时我们就引入了线程池的概念,为程序指定线程数,按使用为程序分配线程。

JDK5之前,必须手动才能实现线程池,从JDK5开始新增了一个Executors工厂类,通过该工厂类可以实现线程池:

 

public static ExecutorService newFixedThreadPool(int nThreads)

创建一个可重用的、具有固定线程数的线程池;

package Mars;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
	public static void main(String[] args) {

		ExecutorService pool = Executors.newFixedThreadPool(2);

		pool.submit(() -> {// 线程1

			for (int i = 0; i < 15; i++) {
				try {
					Thread.sleep(1000);// 该类不是Thread类的子类,所以需要用Thread类名调用
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("计数1: " + i);
			}

		});
		
		pool.submit(() -> {// 线程2

			for (int i = 0; i < 15; i++) {
				try {
					Thread.sleep(1000);// 该类不是Thread类的子类,所以需要用Thread类名调用
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("计数2: " + i);
			}

		});
		
		pool.submit(() -> {// 线程3,因为线程池线程数有限,所以要等线程1,2结束,线程池才会给线程3分配线程

			for (int i = 0; i < 15; i++) {
				try {
					Thread.sleep(1000);// 该类不是Thread类的子类,所以需要用Thread类名调用
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("计数3: " + i);
			}

		});

		
	}
}

 

发布了91 篇原创文章 · 获赞 10 · 访问量 8014

猜你喜欢

转载自blog.csdn.net/Liuxiaoyang1999/article/details/100032626