1、进程和线程
1.1、进程
代表内存中正在运行的程序,计算机会将资源合理的分配每个进程
进程是系统运行一个应用程序的基本单位
当然启动一个应用程序,会有一个或多个进程同时被创建
1.2、线程
线程是进程中的一个代码执行单元
负责将当前进程中的代码程序执行
一个进程有一个或多个线程
每一个进程都会向计算机抢占运行所需的资源,而这些资源最终是要分配给进程中的线程。
当我们使用Java去运行一个Java程序的时候,会启动JVM,而JVM相当于计算机来说,它也是一个应用程序,所以会同时启动进程和JVM对应
1.3、并发和并行
- 线程并发执行,是指在同一个时间段了,多个线程使用同一个cpu,并交替运行
- 线程并行执行,是指同一个时刻,多个线程各自使用不同的CPU,同时进行程序
我们开发程序,都是按照单核CPU来考虑
多核CPU可以提高程序运行速度,但基本不会影响我们代码的设计和实现的。
1.4、时间片
时间片—一个线程允许使用CPU运行的时间
如果时间片结束,那么这个线程必须就要暂停运行。
这是因为线程是交替式的使用CPU。
因为线程交替运行的速度很快,所以我们就会觉得这两个线程同时运行
1.5、调度
当多个线程同时使用一个CPU的时候,就需要相应的算法来控制线程获取CPU时间片的方式,从而使得线程可以按照某种顺序来使用CPU。
常见的调度方式:
-
时间片轮转
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
-
抢占式调度
系统会优先让优先级高的线程使用CPU(优先级高不代表一定可以抢到CPU,只是抢到的概率相对而言比较高),如果线程的优先级相同,那么就是随机选择一个线程获取当前CPU的时间片
JVM中的线程,用的都是抢占式
1.5、main线程
在启动JVM加载一个类的时候,JVM会创建一个名字叫做main的线程,来执行类中的main方法
- 当使用Java运行一个类的时候,会先启动JVM
- 首先通过JVM的类加载子系统,将相应的类信息加载到方法区(静态成员在类加载的时候也会一起加载到方法区)
- 接着JVM会创建并启动main线程
- main线程会加载main方法,并且执行main方法中的代码,输出相应的结果
- 在mian方法中的代码执行完毕,main方法弹栈,main线程结束,JVM也随之停止
注意,这是只有main一个线程的时候的情况,如果存在多个线程,那么JVM就会等到所有线程结束才会停止
1.6、线程的创建和启动
java.lang.Thread 是java中的线程类,所有的线程对象都必须是Thread类或其子类的实例
每个线程的作用,就是完成开发人员给它指定的任务,实际上就是执行一段开人员指定的代码
线程最重要的就是run()方法中的代码,这也是我们所要关注的
通过继承的Thread类中来创建并且启动一个新的线程
- 定义
Thread
类的子类(可以是匿名内部类),并重写Thread
类中的run
方法,run
方法中的代码就是线程的执行任务 - 创建
Thread
子类的对象,这个对象就代表了一个要独立运行的新线程 - 调用线程对象的
start
方法来启动该线程
public class Test {
public static void main(String[] args) {
//2.创建线程类对象
Thread t = new MyThread();
//启动线程才会抢占CPU的时间片
//3.调用start方法启动线程
t.start();
}
}
//1.子类继承父类Thread,并重写run方法(指定线程的执行任务)
class MyThread extends Thread{
@Override
public void run() {
//线程启动以后要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
try {
//可以让当前执行代码的线程睡眠1000毫秒,休眠期间不会让出CPU
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//匿名内部类的方式
public class Test {
public static void main(String[] args) {
Thread t = new MyThread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
}
main线程在执行main方法的过程中,创建并启动了其他线程,并且其他线程启动后,和main线程就没有关系了,这时候main线程和其他线程都是自己独立的运行,并且他们是要争夺CUP的时间片(使用权)的
在JVM中,方法区和堆是所有线程共享的,但栈和PC计数器是每一个线程独有的
线程对象被创建出来之后,只是一个普通的对象,只有当调用了start()方法之后,才算是一个创建了新的线程
线程在启动之后,就会开始抢占时间片,并且自动运行run方法,如果run方法没有重写,那么就会父类的run方法
多线程相比单线程的优势就在于 可以提高程序的运行速度
1.7、Runnable接口
给一个线程对象指定要执行的任务,除了继承Thread类后重写run方法之外,还可以利于Runnable接口来完成线程任务的指定
Runnable接口中只有一个抽象方法,就是run方法
我们可以通过实现这个接口或者使用匿名内部类来实现run方法,
在Thread类中的构造器就有一个构造器就是传一个Runnable
接口的实现类对象进来
例如
public class Test {
public static void main(String[] args) {
//Runnable接口的实现类中,重写了run方法,指定线程的执行任务
Runnable run = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//创建线程对象,指定执行任务
Thread t = new Thread(run);
t.start();
}
}
实现Runnable接口比继承Thread类所具有的优势:
- 可以把相同的一个执行任务(Runnable接口的实现),交给不同的线程对象去执行(这个使用继承也是可以实现)
- 可以避免java中的单继承的局限性。
- 线程和执行代码各自独立,实现代码解耦
1.8、线程的名字
Thread.currentThread().getName(); 通过这个方法可以获得当前线程的名字
注意
public static void main(String[] args){
show();
}
public static void show(){
System.out.println(Thread.currentThread().getName());
}
结果
main
这是因为是在main线程中调用main方法,而main方法中又调用了show()方法,所以还是在main线程中
main线程是程序的主线程
默认情况下,主线程中,创建出的线程,它们的都会有一个默认的名字:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
其中,"Thread-" + nextThreadNum()
就是在拼接出这个线程默认的名字,Thread-0 Thread-1 Thread-2等等
当然我们也可以自己指定线程的名字
Thread t = new Thread("t线程");
//或者
Thread t = new Thread(new Runnable(){
public void run(){
//执行任务
}
//在创建线程的时候指定其名字不能够再线程启动以后再去执行
},"t线程");
//或者
Thread t = new Thread();
t.setName("t线程");
//只有调用start才会启动线程
1.9、线程的分类
java中,线程可以分为:
- 前台线程,又叫做执行线程、用户线程
- 后台线程,又叫做守护线程、精灵线程
前台线程:
这种线程专门用来执行用户编写的代码,
前台线程—main,开发人员自己编写线程,JVM是否关闭取决于前台线程是否执行完
后台线程:
这种线程是用来给前台线程服务的,给前台线程提供一个良好的运行环境,
后台线程—给前台的线程提供良好的运行环境,内存环境,类加载环境等,JVM是否停止运行跟后台线程
当然也可以通过特定的方法使得开发人员编写的代码变成后台线程
public class Test {
public static void main(String[] args) {
Thread t = new Thread("t线程"){
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name+": hello "+i);
}
}
};
//在启动线程之前,可以将其设置为后台线程,否则默认是前台线程
t.setDaemon(true);
t.start();
}
}
1.10、线程优先级
线程的优先级使用int类型数字表示,最大是10,最小是1,默认的优先级是5。
当俩个线程争夺CPU时间片的时候:
- 优先级相同,获得CPU使用权的概率相同
- 优先级不同,那么高优先级的线程有更高的概率获取到CPU的使用权
setPriority()
这个方法就是可以设置线程的优先级,要在线程启动之前设置好,里面传的参数只能是1~10的正正数。