1. 基本概念
-
程序(program)
程序是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码(还没有运行起来),静态对象。
-
进程(process)
进程是程序的一次执行过程,也就是说程序运行起来了,加载到了内存中,并占用了cpu的资源。这是一个动态的过程:有自身的产生、存在和消亡的过程,这也是进程的生命周期。
进程是系统资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
-
线程(thread)
进程可进一步细化为线程,是一个程序内部的执行路径。
若一个进程同一时间并行执行多个线程,那么这个进程就是支持多线程的。
线程是cpu调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
一个进程中的多个线程共享相同的内存单元/内存地址空间——》他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得相乘间通信更简便、搞笑。但索格线程操作共享的系统资源可能就会带来安全隐患(隐患为到底哪个线程操作这个数据,可能一个线程正在操作这个数据,有一个线程也来操作了这个数据v)。
-
配合JVM内存结构了解(只做了解即可)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wJraFAzW-1619229368626)(https://i.vgy.me/karuEk.png)]
class文件会通过类加载器加载到内存空间。
其中内存区域中每个线程都会有虚拟机栈和程序计数器。
每个进程都会有一个方法区和堆,多个线程共享同一进程下的方法区和堆。
-
-
CPU单核和多核的理解
单核的CPU是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。同时间段内有多个线程需要CPU去运行时,CPU也只能交替去执行多个线程中的一个线程,但是由于其执行速度特别快,因此感觉不出来。
多核的CPU才能更好的发挥多线程的效率。
对于Java应用程序java.exe来讲,至少会存在三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。如过发生异常时会影响主线程。
-
Java线程的分类:用户线程 和 守护线程
- Java的gc()垃圾回收线程就是一个守护线程
- 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以吧一个用户线程变成一个守护线程。
-
并行和并发
- 并行:多个cpu同时执行多个任务。比如,多个人做不同的事。
- 并发:一个cpu(采用时间片)同时执行多个任务。比如,渺少、多个人做同一件事。
-
多线程的优点
- 提高应用程序的响应。堆图像化界面更有意义,可以增强用户体验。
- 提高计算机系CPU的利用率。
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
-
何时需要多线程
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
2. 线程的创建和启动
2.1. 多线程实现的原理
- Java语言的JVM允许程序运行多个线程,多线程可以通过Java中的java.lang.Thread类来体现。
- Thread类的特性
- 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常吧run()方法的主体称为线程体。
- 通过Thread方法的start()方法来启动这个线程,而非直接调用run()。
2.2.多线程的创建,方式一:继承于Thread类
- 创建一个继承于Thread类的子类。
- 重写Thread类的run()方法。
- 创建Thread类的子类的对象。
- 通过此对象调用start()来启动一个线程。
**代码实现:**多线程执行同一段代码
package com.broky.multiThread;
/**
* @author 13roky
* @date 2021-04-19 21:22
*/
public class ThreadTest extends Thread{
@Override
//线程体,启动线程时会运行run()方法中的代码
public void run() {
//输出100以内的偶数
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":\t"+i);
}
}
}
public static void main(String[] args) {
//创建一个Thread类的子类对象
ThreadTest t1 = new ThreadTest();
//通过此对象调用start()启动一个线程
t1.start();
//注意:已经启动过一次的线程无法再次启动
//再创建一个线程
ThreadTest t2 = new ThreadTest();
t2.start();
//另一种调用方法,此方法并没有给对象命名
new ThreadTest().start();
System.out.println("主线程");
}
}
多线程代码运行图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PguN728e-1619229368627)(https://i.vgy.me/at2IMI.png)]
多线程执行多段代码
package com.broky.multiThread.exer;
/**
* @author 13roky
* @date 2021-04-19 22:43
*/
public class ThreadExerDemo01 {
public static void main(String[] args) {
new Thread01().start();
new Thread02().start();
}
}
class Thread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
}
}
}
class Thread02 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
}
}
}
2.3.多线程的创建,方式一:创建Thread匿名子类(也属于方法一)
package com.broky.multiThread;
/**
* @author 13roky
* @date 2021-04-19 22:53
*/
public class AnonymousSubClass {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
}
}
}.start();
}
}
2.4. 多线程的创建,方式二:实现Runnable接口
- 创建一个实现Runnable接口的类。
- 实现类去实现Runnable接口中的抽象方法:run()。
- 创建实现类的对象。
- 将此对象作为参数传到Thread类的构造器中,创建Thread类的对象。
- 通过Thread类的对象调用start()方法。
package com.broky.multiThread;
/**
* @author 13roky
* @date 2021-04-20 23:16
*/
public class RunnableThread {
public static void main(String[] args) {
//创建实现类的对象
RunnableThread01 runnableThread01 = new RunnableThread01();
//创建Thread类的对象,并将实现类的对象当做参数传入构造器
Thread t1 = new Thread(runnableThread01);
//使用Thread类的对象去调用Thread类的start()方法:①启动了线程 ②Thread中的run()调用了Runnable中的run()
t1.start();
//在创建一个线程时,只需要new一个Thread类就可,不需要new实现类
Thread t2 = new Thread(runnableThread01);
t2.start();
}
}
//RunnableThread01实现Runnable接口的run()抽象方法
class RunnableThread01 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
}
}
}
2.4.1. 比较创建线程的两种方式
- Java中只允许单进程,以卖票程序TiketSales类来说,很有可能这个类本来就有父类,这样一来就不可以继承Thread类来完成多线程了,但是一个类可以实现多个接口,因此实现的方式没有类的单继承性的局限性,用实现Runnable接口的方式来完成多线程更加实用。
- 实现Runnable接口的方式天然具有共享数据的特性(不用static变量)。因为继承Thread的实现方式,需要创建多个子类的对象来进行多线程,如果子类中有变量A,而不使用static约束变量的话,每个子类的对象都会有自己独立的变量A,只有static约束A后,子类的对象才共享变量A。而实现Runnable接口的方式,只需要创建一个实现类的对象,要将这个对象传入Thread类并创建多个Thread类的对象来完成多线程,而这多个Thread类对象实际上就是调用一个实现类对象而已。实现的方式更适合来处理多个线程有共享数据的情况。
- 联系:Thread类中也实现了Runnable接口
- 相同点两种方式都需要重写run()方法,线程的执行逻辑都在run()方法中
2.5. 多线程的创建,方式三:实现Callable接口
与Runnable相比,Callable功能更强大
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
package com.broky.multiThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。 ---JDK5新特性
* 如何理解Callable比Runnable强大?
* 1.call()可以有返回值
* 2.call()可以抛出异常被外面的操作捕获
* @author 13roky
* @date 2021-04-22 21:04
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable<Integer>{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i < 100; i++) {
if(i%2==0){
System.out.println(i)