线程(执行代码)
进程:代表了内存中正在运行的应用程序
线程:线程是进程中的一个代码执行单元,负责当前进程中代码程序的执行,一个进程中有一个或多个线程。 当一个进程中启动了多个线程去分别执行代码的时候,这个程序就是多线程程序。
并发和并行
线程的并发执行,是指在一个时间段内,俩个或多个线程,使用一个CPU,进行交替运行。(问题主要出在这,我们解决的就是这个并发)
线程的并行执行,是指在同一时刻,俩个或多个线程,各自使用一个CPU,同时进行运行。
如果计算机是单核CPU的话,那么同一时刻只能有一个线程使用CPU来执行代码
如果计算机是多核CPU的话,那么同一时刻有可能是俩个线程同时使用不同的CPU执行代码
时间片
时间片,当前一个线程要使用CPU的时候,CPU会分配给这个线程一小段时间(毫秒级别),这段时间就叫做时间片,也就是该线程允许使用CPU运行的时间,在这个期间,线程拥有CPU的使用权。
如果在一个时间片结束时,线程还在运行,那么这时候,该线程就需要停止运行,并交出CPU的使用权,然后等待下一个CPU时间片的分配。
在宏观上,一段时间内,我们感觉俩个线程在同时运行代码,其实在微观中,这俩个线程在使用一个CPU的时候,它们是交替着运行的,每个线程每次都是运行一个很小的时间片,然后就交出CPU使用权,只是它们俩个交替运行的速度太快了,给我们的感觉,好像是它们俩个线程在同时运行。
调度
当俩个或多个线程使用一个CPU来运行代码的时候,在操作系统的内核中,就会有相应的算法来控制线程获取CPU时间片的方式,从而使得这些线程可以按照某种顺序来使用cpu运行代码,这种情况被称为线程调用。
常见的调度方式:
时间片轮转
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度(大部分/jvm)
系统会让优先级高的线程优先使用 CPU(提高抢占到的概率),但是如果线程的优先级相同,那么会随机选择一个线程获取当前CPU的时间片。
main线程
使用 java 命令来运行一个类的时候,首先会启动JVM(进程),JVM会在创建一个名字叫做main的线程,来执行类中的程序入口(main方法)
public class Test {
public static void main(String[] args) {
//获取执行当前方法的线程对象
Thread currentThread = Thread.currentThread();
System.out.println("执行当前方法的线程名字为:"+currentThread.getName());
}
}
//运行结果:
执行当前方法的线程名字为:main
Thread.currentThread(); 可以写在任意方法中,返回就是执行这个方法的线程对象
线程类
java.lang.Thread
java.lang.Thread 是java中的线程类,所有的线程对象都必须是Thread类或其子类的实例。
每个线程的作用,就是完成我们给它指定的任务,实际上就是执行一段我们指定的代码。我们只需要在Thread 类的子类中重写 run 方法,把执行的代码写入到run方法中即可,这就是线程的执行任务!
Java中通过继承Thread类来创建并启动一个新的线程的步骤如下:
- 定义 Thread 类的子类(可以是匿名内部类),并重写 Thread 类中的 run 方法, run 方法中
的代码就是线程的执行任务 - 创建 Thread 子类的对象,这个对象就代表了一个要独立运行的新线程
- 调用线程对象的 start 方法来启动该线程
public class ThreadTest {
public static void main(String[] args) {
System.out.println("main start");
Thread t = new Thread() {
@Override
public void run() {
for (int i = 1; i <=100; i++) {
System.out.println(getName()+"i:"+i);
}
}
};//创建进程对象
t.start();//启动对象
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 1; i <=100; i++) {
System.out.println(getName()+"i:"+i);
}
}
};
t1.start();
for (int i = 1; i <=100; i++) {
System.out.println("main i:"+i);
}
System.out.println("main end");
}
}
结果如上,可以看出三个线程互相抢夺
Runnable接口
给一个线程对象指定要执行的任务,除了继承Thread类后重写run方法之外,还可以利用Runnable接口来完成线程任务的指定
Thread 类也是 Runnable 接口的实现类,其代码结构大致为
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
public Thread() {
//...
}
public Thread(Runnable target) {
this.target = target;
//..
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
可以看出,子类重写Thread中的run方法,这个run方法其实也来自于Runnable接口
使用 Runnable 接口的匿名内部类,来指定线程的执行任务(重写接口中的run方法)
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中的单继承的局限性。
- 线程和执行代码各自独立,实现代码解耦