多线程
并发、并行
-
并行 :
具有在同一时刻干两件或两件以上事情的能力,几件件事同一时刻进行。(同时执行)
情景 : 正在吃饭,有人打电话过来一边吃饭,一边打电话
当前事未完成事,可以同时干这件事和另一件事
-
并发
具有在某个时间段做两件或两件以上事情的能力,几件事情在同一个时间段进行。(交错执行)
情景: 正在吃饭,有人打电话过来
停下吃饭的动作,接听电话,接听完电话,继续吃饭。
当前事未完成时,可以暂时停止这件事,去干另一件事 -
非并行,非并发
情景: 正在吃饭,有人打电话过来 继续吃饭,吃完饭再接听电话 必须完成当前这件事才能去干另一件事
进程、线程
- 进程
被加载入内存(RAM),正在运行的程序,且具有独立的功能 - 线程
进程的一个执行单元,负责进程中程序的执行,一个进程有多个线程
线程相当于通向cup的一条道路 - 程序、进程、线程 三者的关系
程序 > 进程 > 线程
一个程序至少有一个进程,一个进程至少有一个线程,含有多个线程的程序称为多线程程序。
其他
创建多线程
两种方法:
-
继承Thread类
1.继承Thread类 2.复写run()方法 3.创建线程对象,调用start()方法,开启线程
/**
*创建一个多线程实现Thread类
*/
public class SubThread01 extends Thread {
@Override
public void run() {
for (int j = 0; j < 20; j++) {
System.out.println("subThread====" + j);
}
}
}public class SubThreadDemo01 {
public static void main(String[] args) {
//创建线程
SubThread01 subth = new SubThread01();
//开启线程
subth.start();
for (int i = 0; i < 20; i++) {
System.out.println("main====" + i);
}
}
} -
实现Runable接口
-
实现 Runable接口
-
复写run方法
-
创建线程对象
-
创建Thread类,放入线程对象
Thread th = new Thread(线程对象) -
th.start()
/**
*使用Runable创建一个线程类
*/
public class RunThread01 implements Runnable {
@Override
public void run() {
for (int i = 0; i <20; i++) {
System.out.println("runthread===" + i);
}
}
}public class RunThreadDemo01 { public static void main(String[] args) { //创建线程对象 RunThread01 rth = new RunThread01(); //开启线程 Thread th = new Thread(rth); th.start(); for (int i = 0; i <20; i++) { System.out.println("run_main" + i); } } }
-
继承Thread类和Runable类的区别:
- 使用Runable,避免了单继承的局限性,类继承了Thread类就不能继承其他类。
- 增强了程序的扩建性,降低了程序的耦合性,实现了Runable将设置线程任务和开启新线程进行分离
继承Runable接口,重写run()方法 ------》设置线程任务
使用Thread将线程任务放进去调用start()方法 ------》设置线程任务
使用对象注入的时候可以在配置文件中更改线程任务,不必跑到源码中修改
Jvm开启多线程的原理
- Jvm开启线程
首先Jvm执行程序,执行到main方法时,通知OS系统开辟一条mian方法到CPU的路径,此时这条路径就称之为线程,当执行到另一个线程的start()方法时,Jvm又通知OS系统开辟一条到当前开启的线程的run()的路径,即线程。
简介版:
Jvm通知OS系统开辟一条main() -----> Cpu的线程(路径),主程序继续执行,开启另一个线程 ------->Jvm通知OS开辟一条通往开启线程run()方法的路径(线程)
- 开启多线程时的堆栈分配
每一个线程都分配有自己的栈,公用一个堆
当一个线程停止,不影响另一个线程执行
- 如何实现线程切换
线程的切换实际上CPU在不同线程的栈上进行切换,CPU的执行代码依赖于各种寄存器,当线程被挂起时,就将寄存器的数值放在该线程的堆中,当CPU重新执行此线程时,将从栈中取出寄存器的数值,接着运行。
CPU使用相同线程函数代码在不同的栈中切换
Thread类的常用方法
-
String getName() : 获取线程的名字
该方法只能有一个线程实例才能使用,不能直接Thread.getName(); 使用情况: 1.在线程的run()方法中直接调用 2.通过Thread.currentThread().getName()使用 因为main线程没有实现Runable和继承Thread类,因此不能直接在main方法中调用getName(),要先获取 Thread对象
-
Thread currentThread() :获取当前执行线程
-
setName() :设置线程名字
-
sleep(Long millis) :设置线程睡眠时间
该类会抛出InterrupException:中断异常
public class SubThreadDemo02 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 60; i++) {
Thread.sleep(1000);
System.out.println(i);
}
}
}
使用匿名内部类创建线程
-
什么叫匿名内部类
即隐式的继承一个类或实现一个接口,匿名内部类是一个继承了该类或者实现了该接口的子类匿名对象。 -
匿名内部类的分类
继承一个类
实现类的接口
匿名内部类的最终产物是一个子类或实现类/** * 使用你匿名内部类来创建线程 *匿名内部类特点:子类继承父类、重写方法、建立对象一步完成 */ public class RunThreadDemo03 { public static void main(String[] args) { /** * 创建一个继承Thread的内部类 * 相当于一个多态,子类对象赋给父类对象使用 父类对象 = 子类对象 */ //1.继承父类Thread的匿名内部类 new Thread() { //2.重写方法 public void run() { for (int i = 0; i < 20; i++) { System.out.println(getName() + "=====" + i); } } //3.创建对象,使用方法 }.start(); for (int j = 0; j < 20; j++) { System.out.println("main=====" + j); } /** * 创建一个实现Rununable接口的内部类 * 使用Runnable来接收这个内部类,再放入到Thread中 *//* Runnable r = new Runnable() { public void run() { for (int k = 0; k < 20; k++) { System.out.println(Thread.currentThread().getName() + "=====" + k); } } }; Thread th = new Thread(r); th.start();*/ /** * 直接将匿名内部类作为参数传入 */ new Thread(new Runnable() { @Override public void run() { for (int k = 0; k < 20; k++) { System.out.println(Thread.currentThread().getName() + "=====" + k); } } }).start(); } }
线程调度
线程的调度由jvm虚拟机实现
-
定义
按特定机制给多个线程分配cpu的使用
计算机只有一个cpu,一个cpu在任意时刻只能执行一条机器指令,线程只有获取cpu的执行权才能执行指令
并发运行的意思是线程轮流的获得cpu的使用权,在运行池中,有多个就绪状态的线程等着cpu -
线程调度的分类
- 分时调度
所有线程轮流使用cpu且使用cpu的时间片相同 - 抢占式调度
让运行池中优先级别高的线程先使用cpu,若运行池的线程级别相同则随机选择一个线程获得CPU的执行权
处于运行状态的线程会一直运行,直至它不得不放弃CPU
- 分时调度
-
设置线程的优先级
setPriority() :设置线程优先级
getPriority() :获取线程优先级
其他