一,计算机组成相关知识
在学习多线程之前,我们需要学习一些计算机组成的一些知识。
当去开一个虚拟机,开一个数据库,开git,开xshell,开idea, 开Nginx,开redis,启动N多个微服务。
就需要一台高性能配置的电脑(cpu8核和运行内存16G。)
为了完成特定的任务,用某种编程语言写一个软件(程序),程序要想运行就必须加载到内存中执行。
在执行程序的时候,实时的指令加载到cpu内存的指令寄存器中执行,执行过程中产生的数据要加载到数据寄存器中,ALU负责进行算术逻辑运算的操作。
系统总线(System Bus
):连接计算机系统的主要组件,用来降低成本和促进模块化。 可以通过软件来控制硬件。
进程: 一个正在执行中的程序就是一个进程,系统就会为这个进程发配独立的【运行资源】 进程是程序的一次执行过程,它有自己的生命周期 它会在启动程序时产生 运行程序时存在 关闭程序时消亡
比如:QQ,idea,腾讯会议,PDF 早期的计算机。
单进程:同一时间只能执行一个进程;计算器:同一时间只能执行一段代码。
随着计算机的发展,CPU的计算能力大幅提升, 按照时间线交替执行不同继承的方式。 每个执行一点点时间,感官上觉得这么多的进程是同时在运行。
进程更强调的是【内存资源分配】;线程更强调的是【计算的资源的分配】
因为有了线程的概念,一个进程的线程不能修改另一个线程的数据,线程之间是相互隔离的, 安全性更好。理论上,一个核在一个时间点只能跑一个线程,但是cpu同一个时间能跑16线程
- 物理cpu内核,每颗物理CPU可以有1个或多个物理内核, 通常情况下物理CPU内核数都是固定,单核CPU就只有1个物理内核
- 逻辑CPU内核,操作系统可以使用逻辑CPU来模拟真实CPU。 在没有多核心CPU情况下,单核CPU只有一个核,可以把一个CPU当做多个 CPU使用,逻辑CPU的个数就是作用的CPU物理内核数。
二,如何创建线程
在Java中,创建线程有3种方式
1. 继承Thread
类,并且重写run
方法
Thread
类中的run
方法不是抽象方法,Thread
类也不是抽象类MyThread
当继承了Thread
类之后,它就是一个独立的线程。要让线程启动。调用线程的start
方法。
代码部分
class MyThread extends Thread {
@Override
public void run() {
System.out.println(2);
}
}
public class Ch01 {
public static void main(String[] args) {
System.out.println(1);
MyThread myThread = new MyThread();
myThread.start();
System.out.println(3);
System.out.println(4);
}
}
当调用start
方法启动一个线程时,会执行重写的run
方法的代码
调用的是start
(启动线程),执行的是run
(使用方法)。普通的对象调方法,myThread.run();
线程有优先级,存在概率问题!做不到百分百。可能会优先主方法,然后运行mythread
也就是说,输出结果可能为1,2,3,4
,也有可能是1,3,4,2
等等
使用箭头函数(lambda表达式)
public class Ch03 {
public static void main(String[] args) {
System.out.println(1);
new Thread(() -> System.out.println(2)).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(3);
System.out.println(4);
}
}
由于启动新线程后主线程进入了休眠Thread.sleep
,再执行3.sout 4.sout
输出结果一般为1 2 3 4
2. 实现Runnable
接口,并重写run
方法
代码实现
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println(2);
}
}
public class Ch02 {
public static void main(String[] args) {
System.out.println(1);
MyThread2 myThread2 = new MyThread2();
Thread t = new Thread(myThread2);
t.start();
System.out.println(3);
System.out.println(4);
}
}
如果想要让线程启动,必须调用Thread类中的start方法
但是Runnable
无法使用start
方法,需要先创建Thread
对象,然后将线程对象传进去。
输出与第一个方法相同,存在着优先级随机的可能性。
3. 实现Callable
接口
代码实现
class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(2);
return "call方法的返回值";
}
}
public class Ch04 {
public static void main(String[] args) {
System.out.println(1);
FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
new Thread(futureTask).start();
System.out.println(3);
System.out.println(4);
}
}
一般不使用Callable
,因为它需要层层调用
Callable-->FutureTask-->RunnableFuture-->Runnable-->Thread
守护线程
Java中提供两种类型的线程:
- 用户线程
- 守护程序线程
守护线程为用户线程提供服务,仅在用户线程运行时才需要。
守护线程对于后台支持任务非常有用,他可以垃圾回收。大多数JVM线程都是守护线程。QQ,主程序等就是用户线程。
任何线程继承创建它的线程守护进程状态。由于主线程是用户线程,因此在main
方法内启动的任何线程默认都是守护线程。