线程的基本使用
线程与进程
在资源管理器中常常可以看到进程和一些进程里面的程序,那么这些是什么呢?接下来我们一起探讨
线程与进程的基本概念
- 进程:正在运行的程序,是操作系统分配系统资源(CPU、内存)的最小单位
- 线程:进程是由多个线程组成的,是操作系统调度CPU的最小单元,每个线程可以单独执行指令
进程与线程的区别
进程更加重量级,操作系统创建和销毁进程需要更多的时间和资源,进程的相互通信更加复杂
线程更加轻量级,操作系统创建和销毁线程消耗时间和资源更少,同一个进程的线程可以共享内存空间,通信更容易
为什么使用多线程
- 压榨CPU资源,执行高性能运算
- 同时执行多个程序指令,相互不会影响
- 服务器可以同时服务多个用户,互不影响
并发与并行
-
并发:同时执行多个任务,一个CPU内核会在多个线程间来回切换执行程序指令,不是真正同时执行
-
并行:同时执行多个任务,多个CPU内核,一个内核执行一个线程,线程中指令是同时执行的
同步与异步
-
同步:多个指令是排队执行的,效率比较低
-
异步:多个指令同时执行(借助线程),效率比较高
线程的创建
主要通过四种方式创建
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池
1.继承Thread类,重写run方法,使用start执行
/**
* 继承Thread类
*/
public class MyThread extends Thread{
//重写run方法
@Override
public void run() {
//Thread.currentThread() 当前线程
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "执行了" + i);
}
}
public static void main(String[] args) {
//创建线程,启动线程
//启动线程使用start,为什么不使用run? 使用start才能创建新的线程执行,使用run方法就是在主线程中执行
//多线程是如何执行???CPU的调度是抢占式,每个线程去抢cpu资源,执行过程可以被其它线程抢过去
for (int i = 0; i < 10; i++) {
MyThread thread = new MyThread();
thread.start();
}
//主方法中执行的线程是main
System.out.println(Thread.currentThread().getName() + "执行了");
}
}
2.实现Runnable接口,重写run方法,创建Thread对象时传入Runnble对象,使用start执行
/**
* 自定义Runnable对象
* 更加灵活,继承类后不能继承其它类,实现接口没有任何限制
* 语法更严格,必须重写run方法,继承类后不强制要求
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "执行了" + i);
}
}
public static void main(String[] args) {
// //创建Thread对象,同时传入Runnable对象
// Thread thread = new Thread(new MyRunnable());
// //启动线程
// thread.start();
//使用lambda表达式实现Runnable
for(int i = 0; i < 10;i++){
new Thread(() -> {
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName() + "执行了" + j);
}
}).start();
}
}
}
3.实现步骤
1)实现callable接口
2) 实现call方法,返回结果
3) 创建FutureTask对象,传入Callable对象
4) 创建Thread对象,传入FutureTask对象
5) 调用Thread的start方法
6) 通过FutureTask的get方法获得返回结果
/**
* 自定义Callable对象
*/
public class MyCallable implements Callable<Long> {
@Override
public Long call() throws Exception {
long sum = 0;
//长时间运算
for (int i = 0; i < 100000000L; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName()+"运算完毕!");
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建FutureTask对象,传入Callable对象
FutureTask<Long> futureTask = new FutureTask<>(new MyCallable());
//创建Thread对象,传入FutureTask对象
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
//获得运算结果
System.out.println("结果是:" + futureTask.get());
}
}
继承Thread类和实现Runnable接口的区别
1、继承Thread类不能继承其它类,实现Runnable接口在继承方面没有限制
2、继承Thread类不要求重写run,实现Runnable接口强制要求重写run
推荐使用Runnable
线程的生命周期
- 新建
- 就绪
- 运行
- 阻塞
- 死亡
线程常用方法
方法名 | 说明 |
---|---|
* start() | 启动线程 |
stop() | 停止线程,可能导致重要资源无法释放,出现死锁等问题 |
interrupt() | 中断线程,可以配合异常处理停止线程 |
* run() | 执行线程的核心指令 |
setName(String) | 设置名字 |
getName() | 获得名字 |
* sleep(long) | Thread的静态方法,让当前线程睡眠一定时间(毫秒) |
* setPriority(int) | 设置优先级,从低到高,1到10,优先级高的线程抢占cpu几率更高 |
yield() | 放弃占用CPU一会,马上回到就绪状态 |
suspend() | 禁用,当前线程挂起(暂停) |
resume() | 禁用,当前线程恢复 |
* setDaemon(boolean) | 设置后台线程,默认是false |
join() | 合并其它线程,让其它线程先执行完,再执行自己的代码 |
如何停止线程?
1) stop 禁用,可能导致重要资源无法释放,出现死锁等问题
2) 等待run方法执行结束
3) 在run方法加入条件,中途停止run方法
4) 执行interrupt方法,进行异常处理的时候停止线程
什么是后台线程?
后台线程是一种特殊线程,这种线程是为其它线程服务的,如果没有存活的其它线程,后台线程会自动死亡
后台线程主要应用场景:GC 垃圾收集器 就是一种后台线程
sleep和wait的区别
都可以让线程进入阻塞状态
区别:
- 调用对象不同:sleep是线程调用的,wait是锁对象(Object)调用的
- 释放锁不同:sleep不会让线程释放锁,wait会让线程释放锁
- 唤醒机制不同:sleep只能等睡眠时间结束,wait可以等时间结束也可以通过notify方法唤醒