多线程
线程概念
进程是系统分配资源的最小单位,线程是系统调度的最小单位。一个进程内的线程之间是可以共享资源的
演化过程:
进程 —》 线程(轻量级的进程) —》 协程(轻量级的线程)
线程提升性能
我们用一个线程来完成15亿次的循环,记录运行时间
再用三个线程,每个线程执行5亿次循环,这样一共也是15亿次,记录运行时间
/**
* Created with IntelliJ IDEA.
* Description:进程性能提升
* User: starry
* Date: 2021 -04 -25
* Time: 19:10
*/
public class ThreadDemo3 {
//执行的循环次数
private static final Long count = 5_0000_0000L;
public static void main(String[] args) throws InterruptedException {
// System.out.println(Thread.currentThread().getName());
//调用多线程方法
concorrency();
//调用单线程的方法
serial();
}
//单线程执行方法
private static void serial() {
//开始
Long stime = System.currentTimeMillis(); //记录当前时间毫秒时间戳
// System.nanoTime(); //记录当前时间的纳秒数(更精确)
int a = 0;
for (int i = 0; i < 3 * count; i++) {
a++;
}
Long etime = System.currentTimeMillis();
System.out.println("单线程执行的时间是" + (etime-stime));
}
//多线程的方法
private static void concorrency() throws InterruptedException {
//开始时间
Long stime = System.currentTimeMillis();
//todo: 执行 30 亿次循环
//创建了线程任务
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//具体业务
int a = 0;
for (int i = 0; i < count; i++) {
a++;
}
}
});
//开始执行线程
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
int b = 0;
for (int i = 0; i < count; i++) {
b++;
}
}
});
t2.start();
//让主线程执行 10 亿次
int c = 0;
for (int i = 0; i < count; i++) {
c++;
}
//等待线程 t1 和 t2 执行完成之后,才统计时间
t1.join();
t2.join();
Long etime = System.currentTimeMillis();
System.out.println("多线程执行了" + (etime-stime));
}
}

可以看到多线程的执行时间明显高于单线程
但是为什么3个线程一起执行,时间并不是一个线程的三分之一呢?
因为线程在创建的时候是有性能开销的,并且cpu在调度线程的时候,是抢占式调度,所以也有先后划分
,所以在理想情况下,单线程的时间是n个线程的n倍,但是在实际情况中,肯定会比理想情况下用的时间多
线程的创建方式
方式一:继承 Thread 类
方法一:继承 Thread 类并重写run方法
static class MyThread extends Thread {
@Override
public void run() {
//线程执行任务
System.out.println("线程名称:" +
Thread.currentThread().getName());
}
}
public static void main(String[] args) {
//创建了线程
Thread t1 = new MyThread();
//运行新线程
t1.start();
System.out.println("当前线程的名称(主线程):" +
Thread.currentThread().getName());
}
方法二:使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("线程名:" +
Thread.currentThread().getName());
}
};
t1.start();
继承Thread类的方式的缺点:
Java 语言的设计中,只能实现单继承,如果继承了Thread类,也就不能继承其他类了。
方式二: 实现 Runnable 接口
Java不能多继承,但是可以实现多个接口
(3种写法)
方法1:构建类来实现Runnable接口,创建该类,新建线程
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程名:" +
Thread.currentThread().getName());
}
}
public static void main(String[] args) {
//1.新建 Runnable 类
MyRunnable runnable = new MyRunnable();
//2.新建 Thread
Thread thread = new Thread(runnable);
//3.启动线程
thread.start();
}
方法2:使用匿名类创建 Runnable 子类对象
//匿名内部类的方式实现线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" +
Thread.currentThread().getName());
}
});
thread.start();
方式3:lambda + 匿名 runnable的实现方式
//lambda + 匿名 runnable的实现方式
Thread thread = new Thread(() -> {
System.out.println("线程名:" +
Thread.currentThread().getName());
});
thread.start();
方式三:实现 Callable 接口
(1种写法)
创建并得到线程的执行结果
实现 Callable 接口 + Future 的方式
//创建了线程任务和返回方法
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//生成一个随机数
int num = new Random().nextInt(10);
System.out.println("子线程:" +
Thread.currentThread().getName() + ",随机数:" + num);
return num;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建一个 Callable
MyCallable myCallable = new MyCallable();
//2.创建一个 FutrueTask 对象接收返回值
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
//3.创建线程
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
//得到线程结果
int result = futureTask.get();
System.out.println(String.format("线程名:%s,数字%d",Thread.currentThread().getName(),result));
}
获取当前线程引用
| 方法 | 说明 |
|---|---|
| public static Thread currentThread(); | 返回当前线程对象的引用 |
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
线程休眠
线程休眠一共有 3 种方式:
- 方式一:
Thread.sleep(1000); - 方式二:
TimeUnit.SECONDS.sleep(1);(SECONDS为秒单位,还有day,month等) - 方法三:
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
休眠当前线程
| 方法 | 说明 |
|---|---|
| public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
| public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}
练习:使用两个线程来打印“AABBCCDD”
要点在于休眠,在每次线程打印后都休眠以下,这样两个线程就会同时打印A,而不是一个线程一次性打印完ABCD
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
char a = 'A';
for(int i = 0; i < 4; i++) {
System.out.print((char)(a+i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
char b = 'A';
for(int i = 0; i < 4; i++) {
System.out.print((char)(b+i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start();
}
Thread 的常见构造方法
| 方法 | 说明 |
|---|---|
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
| Thread(String name) | 创建线程对象,并命名 |
| Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
| Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即使线程组 |
代码演示
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
ThreadGroup:线程分组
可以将一类线程归为一组,并且进行信息的打印,查看一组线程的具体行为。
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("开始起跑了" +
Thread.currentThread().getName());
int num = 1 + new Random().nextInt(5);
try {
Thread.sleep(num * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我到终点了:" +
Thread.currentThread().getName());
}
};
//定义分组
ThreadGroup group = new ThreadGroup("百米赛跑第一组");
//创建运动
Thread t1 = new Thread(group,runnable,"张三");
Thread t2 = new Thread(group,runnable,"李四");
t1.start();
t2.start();
// 打印线程分组的详情
// group.list();
//等待所有选手到达
while (group.activeCount() != 0) {
}
//宣布成绩
System.out.println("宣布成绩");
}
使用线程的activeCount()方法,如果线程活跃度不为null,就原地等待,这样就会等两个线程都到终点后,才宣布成绩。
运行结果:
Thread 常见属性
| 属性 | 获取方法 |
|---|---|
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |
代码演示
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"张三");
System.out.println("线程状态:" + t1.getState()); //NEW
t1.start();
System.out.println("线程状态:" + t1.getState()); //RUNNABLE
System.out.println("线程ID:" + t1.getId()); //12
System.out.println("线程名称:" + t1.getName()); //张三
System.out.println("线程优先级:" + t1.getPriority()); //5
System.out.println("线程是否为后台进程:" + t1.isDaemon()); //false
System.out.println("线程是否存活:" + t1.isAlive()); //true
System.out.println("线程是否被中断:" + t1.isInterrupted()); //false
线程优先级
新创建的线程默认优先级是 5
’
线程优先级为 1 - 10 ,越大,执行权重越大,越重要,先执行
这里举个例子
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程名:" +
Thread.currentThread().getName());
}
};
for (int i = 0; i < 10; i++) {
Thread t1 = new Thread(runnable,"小优先线程");
Thread t2 = new Thread(runnable,"中小优先线程");
Thread t3 = new Thread(runnable,"大小优先线程");
t1.setPriority(1); //最小优先级
// t1.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
}
结果
这里可以看到,我们预期的是优先级高的先执行,但是我们从结果可以看出,并不是这样的。
那这里为什么是交替出现的,并没有根据我们预期的优先级打印呢?
其实,即使设置了线程的优先级,一样无法确保这个线程一定先执行,因为它有很大的随机性。它并无法控制执行哪个线程,因为线程的执行,是抢占资源后才能执行的操作,而抢点资源时,最多是给于线程优先级较高的线程一点机会而已,能不能抓住可是不一定的。。
说到底就一句话:线程优化级较高的线程不一定先执行。
后台线程
线程的类型:
- 后台线程(守护线程)
- 用户线程(默认线程)
两者关系:守护线程是用来服务用户线程的,用户线程就是上帝,守护线程就是服务员
进程退出条件:没有用户线程运行的时候进程就i会结束
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("i:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
System.out.println("t1是否为守护线程:" + t1.isDaemon());
}
这里可以看到此用户线程把所有数字打印完了
我们给线程设置为守护线程**t1.setDaemon(true);**后
可以看到此守护线程就打印了一次,因为守护线程是用来服务的,优先级很低,当没有用户线程时,守护线程就不会服务,当然就不会执行,所以就打印了一次。
使用场景:Java 垃圾回收器、TCP的健康检测
注意事项:
- 守护线程的设置必须在开启线程之前(如果设置守护线程在开始线程之后,那么程序就会报错,并且设置的守护线程值不能生效)
- 在守护线程里面创建的线程默认情况下全部是守护线程
启动线程
覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。
- 覆写 run 方法是提供给线程要做的事情的指令清单
- 线程对象可以认为是把 李四、王五叫过来了
- 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了。
start 和 run 之间的区别?
- run 属于普通方法;而 start 属于启动线程的方法
- run 方法可以执行多次,而 start 方法只能执行一次
中断线程
中断线程的三种方式:
- 使用全局自定义的变量来终止线程
- 使用线程提供的终止方法 interrupt 来终止线程
- 使用线程提供的 stop 来终止线程(被废弃的方法)
使用全局自定义的变量来终止线程
举例说明
// 全局自定义变量
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
//转账线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("别烦我,我正在转账呢");
}
System.out.println("终止转账");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(310);
} catch (InterruptedException e) {
e.printStackTrace();
}
//改变变量的状态来终止线程的进行
System.out.println("停止交易,有内鬼");
flag = true;
}
});
t2.start();
t1.join();
t2.join();
}

可以看到它并没有立刻终止线程,因为改变状态时,t1线程已经进入循环并休眠了,sleep期间不能打断。
所以得到结论:
该终止线程的方法比较温柔,再拿到终止指令之后,需要执行完当前的任务才会真正的停止线程
使用 interrupt 来终止线程
| 方法 | 说明 |
|---|---|
| public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,清除中断标志变,否则设置标志位 |
| public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 , |
| public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
举例说明:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//判断线程的终止状态
while (!Thread.interrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// e.printStackTrace();
break;
}
System.out.println("别烦我,我正在转账");
}
System.out.println("终止线程");
}
});
t1.start();
Thread.sleep(310);
//终止线程
System.out.println("有内鬼,终止交易");
t1.interrupt(); //用来终止线程
}

对比可以看出来:
- 使用全局自定义标识终止线程执行的时候,比较温柔,收到终止指令后,会把手头的任务执行完再执行。
- 使用 interrupt 在收到终止指令之后会立马结束执行。
注意:这里的 interrupted 并不是就直接终止线程,而是给线程这样一个终止指令,具体怎么终止的需要依靠代码设置,比如这段代码在休眠的时候终止线程会抛出异常,我们在捕获到异常后break,使其跳出循环,不执行后面代码,所以这个break才是真正终止线程的具体代码。
当有异常的时候,线程会终止,isInterrupted()状态改为true
但是如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志,isInterrupted()变为默认状态 false ,这就是为什么上述代码要加break来终止,否则就会死循环。
Thread.interrupted()和Thread.currentThread().isInterrupted()的区别
Thread.interrupted():执行判断线程终止位 true 之后,就会将状态重置为 false ,因为是全局的
Thread.currentThread().isInterrupted():只能使用一次,不会复位线程的终止状态,因为是私有的
例如:
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
}
}
});
t1.start();
t1.interrupt();
}
如果用 Thread.currentThread().isInterrupted() 结果会全是 true
如果用 Thread.interrupted() 结果第一个为true,后面的全为 false
这也就证明了上述结论。
等待线程
join使用方式
| 方法 | 说明 |
|---|---|
| public void join() | 等待线程结束 |
| public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
| public void join(long millis, int nanos) | 同理,但可以更高精度 |
代码举例
public static void main(String[] args) throws InterruptedException {
//定义公共任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
"上班");
try {
//表示工作时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"下班");
}
};
//定义星星线程
Thread t1 = new Thread(runnable,"星星");
t1.start();
//等待星星执行完成
t1.join();
// t1.join(900);
//定义狒狒线程
Thread t2 = new Thread(runnable,"狒狒");
t2.start();
}
如果不等待的话就是同时上班,同时下班
如果等待的话就是星星先上班,上完班后,狒狒接班上
线程状态
线程状态是一个枚举类型,我们通过代码获取到所有线程状态
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
- NEW:新建状态,没有调用线程 start() 之前的状态
- RUNNABLE:运行状态(Running执行中,Ready就绪)
- BLOCKED:阻塞状态
- WAITING:等待状态,没有明确的等待结束时间,wait() 没有任何参数
- TIMED_WAITING:超时等待状态,有明确的等待结束状态,比如sleep
- TERMINATED:终止状态
代码举例1
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("没调用start之前的状态:" + t1.getState());
t1.start();
System.out.println("调用start之后的状态:" + t1.getState());
Thread.sleep(100); //确保t1线程在休眠期间,主线程先休眠一会
System.out.println("t1的状态:" + t1.getState());
//等待线程执行完成
t1.join();
//也可以让主线程空转来等待t1结束
// while (!t1.isAlive()) {
// }
System.out.println("t1线程执行完成之后的状态:" + t1.getState());
}
执行结果:
代码举例2
public static void main(String[] args) throws InterruptedException {
//创建一把锁
Object obj = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
// 表示线程进行无期休眠状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
System.out.println("没调用start之前的状态:" + t1.getState());
t1.start();
System.out.println("调用start之后的状态:" + t1.getState());
Thread.sleep(100); //确保t1线程在休眠期间,主线程先休眠一会
System.out.println("t1的状态:" + t1.getState());
}
执行结果:
可以看到用 wait() 无限期休眠状态下是 WAITING
线程状态图

yiled关键字
作用:出让 cpu 的执行权
特点:不一定能正常出让 cpu 的执行权
private static final int maxSize = 1000;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < maxSize; i++) {
//出让 CPU 执行权
Thread.yield();
System.out.println("我是t1");
}
});
t1.start();
Thread t2 = new Thread(() -> {
for (int i = 0; i < maxSize; i++) {
System.out.println("我是t2");
}
});
t2.start();
}
如果 t1 线程使用 yield 方法出让执行权,执行结果是 t1 最后执行完
如果 t1 线程不使用 yield 方法出让执行权,执行结果是 t2 最后执行完
虽然说 t1 出让了执行权,但是也并不是全部出让了,t1 和 t2 都会执行,这也应证了不一定能正常出让 cpu 的执行权,这和cpu的调度有关