进程:是一个正在执行的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
一个进程中至少有一个线程。
创建线程的第一种方式:继承Thread类
步骤:
1,定义类继承Thread
2,复写Thread类中的run方法
3,调用线程的start方法。该方法两个作用:启动线程,调用run方法。
多线程的一个特性:随机性,谁抢到谁执行,至于执行多长,cpu说了算。
为什么要覆盖run方法呢?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
static Thread currentThread() 获取当前线程对象
创建线程的第二种方式:实现Runnable接口
步骤:
1,定义类实现Runnable接口
2,覆盖Runnable接口中的run方法(将线程要运行的代码存放在该run方法中)
3,通过Thread类建立线程对象
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数(为什么要将Runnable接口的子类对象传递给Thread的构造函数。因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去执行指定对象的run方法。就必须明确该run方法所属的对象)
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
实现方式和继承方式的区别?
实现方式好处:避免了但继承的局限性
在定义线程时,建议使用实现方式。
两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类run方法。
进程:正在进行中的程序。代表的是应用程序在内存中的一片空间。也就是一个应用程序在内存在开辟的一个空间。
负责进程中代码执行的称之为:线程(负责运算的)。
一个进程中至少要有一个线程。如果有多个线程,就称之为多线程程序。
系统中多任务同时执行的原理:cpu的快速切换(只有多核才能真正实现)
多线程好处:合理的利用了cpu的资源,让多部分代码同时执行。
弊端:过多的线程会降低效率。
什么时候使用多线程技术?
当需要多部分代码同时执行时,就需要多线程技术了。
如何创建线程?
方式一: 继承Thread类
步骤:1,定义一个类继承Thread类。
2,重写Thread类中的run方法。将需要让线程执行的任务代码存储到run方法中。
3,创建Thread类的子类对象(创建一条线程)。
4,调用Thread类中的start方法,开启线程,并执行run方法中的内容(运行的是Thread子类的run方法)。
理解四步骤的原理:
1,首先,JVM启动时就会有一个执行线程负责main方法的代码,说明每一个执行线程的出现都有自己要执行的代码,这段被线程执行的代码称之为:线程的任务。对于JVM启动的负责执行main方法的线程,称之为主线程(执行主函数代码的线程)。
2,想要创建线程去执行指定的代码。需要自己定义一个线程和主线程实现同时执行。自定义的线程也要有线程的任务。这个任务也要像主线程一样有特定存储的位置。
3,描述线程事物的类是Thread,这个类中有定义存储线程执行任务的位置,这个位置就是run方法。
简单的说,run方法中存放的就是线程的任务代码。
接下来,创建线程并启动线程可以写成这个样子。
创建线程对象 Thread t = new Thread();
开启线程 t.start();开启线程,调用Thread类中的run方法。但是这样做没有意义,因为run方法中并未定义我们需要执行的代码。
那么,我么要执行的代码怎么样才能让自定义的线程来执行呢?
只能把这个代码存储到run方法中,可是Thread类中的run方法我们无法进行修改,只能重写,重写就需要继承。所以需要继承Thread类,并重写run方法,定义线程任务。
每一个线程都有自己的名称来标识。
有一些线程有特定的名称(主线程就叫:main),自定义创建的线程有规律的名称,Thread-编号 ,从0开始。
为了在线程中体现线程的名称:可以通过Thread中的currentThread方法获取当前线程的执行线程对象。
再通过getName获取到当前线程对象的名称。
Thread.currentThread().getName();
面试题:调用run和调用start有什么区别?
调用run方法,不开启线程。
调用start方法,开启线程,并让该线程的run方法在新线程中执行。
调用run方法,不开启线程。
调用start方法,开启线程,并让该线程的run方法在新线程中执行。
并发的原理:栈内存中每一个线程都有自己的运行栈空间。
catch (Exception e)
{
System.out.println(e.toString());//异常名称+异常信息
System.out.println(e.getMessage());//异常信息。
e.printStackTrace();//异常名称+异常信息+异常位置 //jvm默认调用了异常的这个方法进行了默认处理,只不过在此基础上,+ 异常所在的线程。
}
{
System.out.println(e.toString());//异常名称+异常信息
System.out.println(e.getMessage());//异常信息。
e.printStackTrace();//异常名称+异常信息+异常位置 //jvm默认调用了异常的这个方法进行了默认处理,只不过在此基础上,+ 异常所在的线程。
}
创建线程的方式二:实现Runnable接口
步骤:
1,定义类实现Runnable接口。
2,重写Runnable接口中run方法。
3,创建Runnable接口中的子类对象。
4,创建Thread类的对象,并将Runnable的子类对象作为参数传递给Thread类的构造函数。
5,调用Thread类中的start方法,开启线程,并执行Runnable接口子类的run方法。
第二种创建线程方式的好处:
1,避免了单继承的局限性。
2,将线程任务单独封装成了任务对象,和线程对象进行解耦。(线程对象和任务对象分开单独封装,就如同一条路(线程)上可以跑各种各样的车(任务)。)
建议使用第二种。
面试题:
1,创建线程的两种方式。
2,两种方式的区别。答:第二种方式的好处即可。
1,创建线程的两种方式。
2,两种方式的区别。答:第二种方式的好处即可。
为什么要传递Runnable接口的对象?
实验:不传。编译通过,运行没有结果。
当调用start时,其实调用的是Thread类中的run方法。而这个run里面并不是我们要执行的线程任务代码。 说明这个线程对象和我们定义的线程任务代码没关系。
目的:将Runnable接口子类对象run方法的线程任务执行。
定义的线程任务在哪里呢?在Runnable接口的子类run方法中。
想让线程去执行Runnable接口子类中的run方法,需要将这个线程任务告诉给线程对象。
怎么告诉呢?线程任务都封装到了run方法中,run方法需要被对象(Runnable接口的子类对象)调用,
只需要将这个子类对象传递给线程对象即可。传递给构造函数的原因是因为线程对象一创建就需要有指定的任务,可以通过构造函数去实现。
实验:不传。编译通过,运行没有结果。
当调用start时,其实调用的是Thread类中的run方法。而这个run里面并不是我们要执行的线程任务代码。 说明这个线程对象和我们定义的线程任务代码没关系。
目的:将Runnable接口子类对象run方法的线程任务执行。
定义的线程任务在哪里呢?在Runnable接口的子类run方法中。
想让线程去执行Runnable接口子类中的run方法,需要将这个线程任务告诉给线程对象。
怎么告诉呢?线程任务都封装到了run方法中,run方法需要被对象(Runnable接口的子类对象)调用,
只需要将这个子类对象传递给线程对象即可。传递给构造函数的原因是因为线程对象一创建就需要有指定的任务,可以通过构造函数去实现。