1 概念
1.1线程是什么
线程是CPU调度的基本单位,它是在进程内部运行的执行流,线程比进程粒度更细,调度成本更低
一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码.
1.2为啥要有线程
首先, "并发编程" 成为 "刚需".
1.单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU.而并发编程能更充分利用多核 CPU 资源
2.有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编 程.
其次, 虽然多进程也能实现并发编程, 但是线程比进程更轻量.
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.
最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程" (Coroutine)
1.3进程和线程的区别(重要)
1.进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
2. 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
进程是系统分配资源的最小单位,线程是系统调度的最小单位。
2第一个多线程程序
感受多线程程序和普通程序的区别:
1.每个线程都是一个独立的执行流
2.多个线程之间是 "并发" 执行的.
package thread;
import java.util.Random;
public class ThreadDemo {
private static class MyThread extends Thread {
@Override
public void run() {
Random random = new Random();
while (true) {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
// 随机停止运行 0-9 秒
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
Random random = new Random();
while (true) {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
// 随机停止运行 0-9 毫秒
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果:
使用jconsole命令观察线程(jconsole在jdk的bin包下)
3.创建线程
方法1:继承Thread类
1.继承Thread来创建一个线程类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
2.创建MyThread实例
MyThread t = new MyThread();
3.使用start()方法启动线程
t.start(); // 线程开始运行
方法2:实现Runnable接口
1.实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
2. 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
3.使用start()方法启动线程
t.start(); // 线程开始运行
对比上面两种方法:
继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()
其他方式(lambda、匿名内部类)
匿名内部类创建 Thread 子类对象:
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
匿名内部类创建 Runnable 子类对象:
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
lambda 表达式创建 Runnable 子类对象:
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
System.out.println("使用匿名类创建 Thread 子类对象");
});
4 多线程的优势-增加运行速度
可以观察多线程在一些场合下是可以提高程序的整体运行效率的
使用System.nanoTime()可以记录当前系统的纳秒级时间戳
serial串行的完成一系列运算,concurrency使用两个线程并行的完成同样的运算
package thread;
public class ThreadAdvantage {
// 多线程并不一定就能提高速度,可以观察, count 不同,实际的运行效果也是不同的
private static final long count = 10_0000_0000;
public static void main(String[] args) throws InterruptedException {
// 使用并发方式
concurrency();
// 使用串行方式
serial();
}
private static void concurrency() throws InterruptedException {
long begin = System.nanoTime();
// 利用一个线程计算 a 的值
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
}
});
thread.start();
// 主线程内计算 b 的值
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
// 等待 thread 线程运行结束
thread.join();
// 统计耗时
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("并发: %f 毫秒%n", ms);
}
private static void serial () {
// 全部在主线程内计算 a、b 的值
long begin = System.nanoTime();
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("串行: %f 毫秒%n", ms);
}
}
运行结果: