文章目录
一、线程的基本概念
1.1 进程和线程
- 进程:正在运行的程序,需要内存和CPU资源进行运算执行,
一个程序包含一个或多个进程 - 线程:是进程的组成单元,一个进程包含一个或多个线程,每个线程负责单独执行一个任务
1.2 进程和线程的区别
- 一个进程包含一个或多个线程
- 每个进程都有自己独立的内存空间,线程没有自己独立的内存空间,线程共享所在进程的内存空间
- 进程是重量级的单元,需要系统资源比较多,线程是轻量级单元,需要资源比较少
1.3 多进程和多线程
- 多进程是操作系统可以同时运行多个进程。一个CPU内核一个时间只能执行一个进程,CPU会在多个进程之间进行来回切换,因为速度特别快,用户感觉不到。
- 多线程是一个进程里面有多个线程,CPU执行进程时会来回切换里面所有的线程,每个线程会分配到一定的CPU的执行时间(CPU时间片)
1.4 多线程的应用场景
- JAVAEE企业级开发:大量的用户需要同时访问网站的服务器,如:双十一、秒杀等。如果服务器只有一个线程,多个用户需要排队和服务器通信,效率非常低;多线程就是一个用户连接服务器后,服务器就会开一个新线程负责用户的通信,用户之间就不会相互影响。
- 游戏开发:同时进行网络通信、游戏角色控制、图形绘制等操作,必须每个用一个线程执行。
1.5 并行和并发
- 并发:一个CPU在多个线程之间快速切换,达到同时执行多个任务的目的
- 并行:多个CPU可以同时执行一个进程中的多个线程
二、线程的实现
2.1 继承Thread类
- 继承Thread类
- 重写run方法
- 创建线程对象,调用start方法
/**
* 自定义线程类
* @author xray
*
*/
public class MyThread extends Thread{
/**
* 执行线程任务的方法
*/
public void run(){
//Thread.currentThread()是获得系统当前执行的线程
System.out.println(Thread.currentThread().getName()+"线程执行了!!!");
}
public static void main(String[] args) {
//主线程中执行
System.out.println(Thread.currentThread().getName()+"线程执行了!!!");
//创建线程对象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
//启动线程
thread1.start();
thread2.start();
}
}
2.2 实现Runnable接口
- 实现Runnable接口
- 实现run方法
- 创建自定义线程对象,作为参数传入Thread对象
- 调用start方法
/**
* 自定义线程类
* @author xray
*
*/
public class MyRunnable implements Runnable{
/**
* 实现run方法
*/
@Override
public void run() {
System.out.println("当前执行的线程是:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
//创建Thread对象,传入Runnable对象
Thread thread1 = new Thread(new MyRunnable());
//调用start方法
thread1.start();
//使用匿名内部类实现Runnable
Thread thread2 = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("匿名内部类,当前执行的线程是:"+Thread.currentThread().getName());
}
});
thread2.start();
//使用Lambda实现Runnable
Thread thread3 = new Thread(()->{
System.out.println("Lambda,当前执行的线程是:"+Thread.currentThread().getName());
});
thread3.start();
}
}
2.3 实现Callable接口
前面两种方法都不能返回结果,Callable的方法可以返回值
- 实现Callable接口,实现call方法
- 创建FutureTask对象,传入Callable对象
- 创建Thread对象,传入FutureTask对象
- 调用Thread对象的start方法
- 调用FutureTask对象的get方法,获得返回值
/**
* Callable的实现类
* @author xray
*
*/
public class MyCallable implements Callable<Long>{
@Override
public Long call() throws Exception {
System.out.println("执行call的线程是:"+Thread.currentThread().getName());
//模拟执行耗时运算
Long sum = 0L;
for(int i = 0;i < 100000000L;i++){
sum += i;
}
//返回结果
return sum;
}
public static void main(String[] args) {
//创建FutureTask对象,传入Callable对象
FutureTask<Long> task = new FutureTask<>(new MyCallable());
//创建Thread,传入FutureTask对象
Thread thread = new Thread(task);
//启动线程
thread.start();
//获得结果
try {
// long result = task.get();
long result = task.get(5,TimeUnit.SECONDS);
System.out.println("获得计算结果:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
Thread、Runable、Callable的区别:
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就现runnable。当需要拿到线程的返回值的时候就可以去使用Callable去实现。
三、线程的生命周期
- 新建
线程new出来,没有调用start方法,CPU没有分配时间片 - 就绪
线程调用了start方法,CPU准备分配时间片,但是没有真正分配 - 运行
线程抢到了CPU,开始执行run方法 - 阻塞
进入阻塞状态:- 调用sleep方法
- 线程的锁调用wait方法
- 调用suspend(挂起)方法
- 流的IO操作
从阻塞恢复: - sleep的时间完毕
- 锁调用notify或notifyAll方法
- 调用resume(恢复)方法
- IO操作完毕
阻塞状态结束后,回到就绪状态
- 死亡,run方法执行完毕后线程进入死亡状态