什么是多线程?
线程是操作系统进行时间片分配调度的最小单位
进程是操作系统资源分配的最小单位
在每个进程中,至少都有一个线程(主线程)
在每个进程中,也允许拥有多个线程
创建线程
public class 手动创建线程 {
static class MyThread extends Thread {
@Override
public void run() {
}
}
/*
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// t1 和 t2 这两个引用指向了各自的线程对象
// 线程最终要执行是 run 方法中的代码
}
*/
static class MyRunnable implements Runnable {
@Override
public void run() {
}
}
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
MyRunnable mr2 = new MyRunnable();
// mr1 和 mr2 引用指向各自的 任务对象
// 最终线程需要有线程对象
Thread t1 = new Thread(mr1); // mr1 指向的任务作为 t1 线程的任务
Thread t2 = new Thread(mr1); // mr1 指向的任务作为 t2 线程的任务
Thread t3 = new Thread(mr2); // mr2 指向的任务作为 t3 线程的任务
}
}
让线程运行就有两种方法:
- 创建好线程后,p.start(); 就会执行线程里run的代码
PrimeThread p = new PrimeThread(143);
p.start();
- 创建好线程后再继承Runnable的run方法,创建出一个任务,然后把任务交给线程
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
多线程情况下可能会提升速度,但一味的增加线程是不会一直提升速度的。因为抢到的CPU是有极限的,线程越多,耗费掉的创建/销毁的时间成本就越高,OS负担会变大,会降低速度。
比如单线程时间是6s, 10个线程是 2s,为什么不是原来的十分之一?
两种情况:排队的除了此线程外还有其他线程,线程本身创建/销毁也是需要时间
在某些场景下必须使用多线程,最常见的就是使用阻塞式IO的场景
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
这个方法调用返回时间是无法预期的,所以什么时候用户输入了才会返回
举个例子:不断的读取用户给的数字,计算这个数字的斐波那契数,要求即使上一个数字还在计算中,也不耽误下一个数字的计算
情况一:不使用多线程
public class 线程的优势_提升速度 {
private static final long COUNT = 1000000000L;
private static final int N = 10; // 一共计算 10 次
private static void calc() {
long r = 0;
for (long i = 0; i < COUNT; i++) {
r += i;
}
System.out.println(r);
}
private static class CalcThread extends Thread {
@Override
public void run() {
// 一个线程只需要计算一遍 calc
calc();
}
}
public static void main(String[] args) throws InterruptedException {
long start = System.nanoTime();
for (int i = 0; i < N; i++) {
calc();
}
long end = System.nanoTime();
double 耗时ms = (end - start) * 1.0 / 1000 / 1000;
System.out.println("耗时: " + 耗时ms);
}
}
结果如下:
情况二:使用多线程
public class 线程的优势_提升速度 {
private static final long COUNT = 1000000000L;
private static final int N = 10; // 一共计算 10 次
private static void calc() {
long r = 0;
for (long i = 0; i < COUNT; i++) {
r += i;
}
System.out.println(r);
}
private static class CalcThread extends Thread {
@Override
public void run() {
// 一个线程只需要计算一遍 calc
calc();
}
}
// 主线程一个线程在计算
public static void main(String[] args) throws InterruptedException {
long start = System.nanoTime();
CalcThread[] threads = new CalcThread[N - 1];
// N 个线程同时执行,其实主线程不需要我创建,所以我需要创建 N - 1 个线程
for (int i = 0; i < N - 1; i++) {
// 创建了 N - 1 个线程,每个线程都执行一次
CalcThread thread = new CalcThread();
thread.start();
threads[i] = thread;
}
calc(); // 剩余的一次,放到主线程中来计算
// 什么时候记录结束时间?
// 代码走到的这里时,只代表主线程中的 calc 计算完了,但
// 另外的 N - 1 个线程是否计算完成,是不知道的
// 所以,我需要等另外 N - 1 个线程全部完成,才结束计时
for (int i = 0; i < N - 1; i++) {
threads[i].join(); // 阻塞在这个方法上,直到 threads[i] 线程执行结束
}
// 这时代表所有的线程都执行结束了
long end = System.nanoTime();
double 耗时ms = (end - start) * 1.0 / 1000 / 1000;
System.out.println("耗时: " + 耗时ms);
}
}
所以在数据计算较多的情况下,使用多线程可以降低时间。
start()方法
start就是启动了一个线程
给了抢CPU的资格,但是并没有真正给它CPU使用资格
所以当前CPU的线程被调度下来,JVM在选择合适的线程分配CPU时,选中了t,把t调度到CPU上,才会运行
Thread.currentThread()
这个静态方法可以返回当前线程的引用
这个方法在哪个线程中被调用,返回的就是哪个线程的引用
public class 查看一个线程的主要属性 {
private static void 打印当前线程的属性() {
Thread thread = Thread.currentThread();
long id = thread.getId();
System.out.println(id + ": 线程的唯一标识: " + id);
String name = thread.getName();
System.out.println(id + ": 线程的名字: " + name);
int priority = thread.getPriority();
System.out.println(id + ": 线程的优先级: " + priority);
Thread.State state = thread.getState();
System.out.println(id + ": 线程的当前状态: " + state);
boolean alive = thread.isAlive();
System.out.println(id + ": 线程是否还活着: " + alive);
boolean interrupted = thread.isInterrupted();
System.out.println(id + ": 线程是否被通知停止: " + interrupted);
boolean daemon = thread.isDaemon();
System.out.println(id + ": 线程是否是后台线程: " + daemon);
}
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
打印当前线程的属性();
}, "张三");
t1.start();
// 主线程还在 CPU 上呢(根据我的现象)
Thread t2 = new Thread(() -> {
打印当前线程的属性();
}, "李四");
t2.start();
// 主线程还在 CPU 上呢(根据我的现象)
打印当前线程的属性();
}
}
此代码经过多次运行,得到不同的结果
相同的代码出现不同的现象是因为线程在CPU执行时,什么时候被切换下来时随机的,线程在争夺CPU时,哪个线程挣到的CPU是随机的
乱序只在线程和线程之间,线程内部不是乱序是有序的
t.join()
等待一个线程(t)完全停止
意味着t要分配CPU了,直到t线程完全结束,调用线程才会重新拥有抢CPU的资格
当执行到 t.join() 时 就不会再往下执行了,直到t结束,下面的语句才会执行到。