进程和线程
进程
:正在运行的软件
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
- 动态性:进程的实质是程序的一次执行过程,进程是动态产生、动态消亡的。
- 并发性:任何进程都可以同其他进程一起并发。
线程
:是进程中的单个顺序控制流,是一个执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序。
- 多线程:一个进程如果是有多条执行路径,则称为多线程程序。
线程
1、每一个线程都会有自己独立的栈内存空间
2、堆内存中的数据,是被多个线程所共享的
eg:多线程消费同一个商品,就可以将商品定义成共享资源,存放于堆内存中
线程和主线程区别
线程:
是进程中执行任务的最小单元,负责使用资源在每一个进程(就是程序运行中)至少一条线程(主线程)我们也可以自己创建子线程帮助我们执行任务。
主线程:
由系统程序自动创建,有且只有一个,我们创建的都是子线程
并发和并行
并行:在同一时刻,有多个指令在多个CPU上同时执行。(同一时刻,三道菜由三位厨师做,每人做一道)
并发:在同一时刻,有多个指令在单个CPU上交替执行。(统一时刻,三道菜由一个厨师做,一次做其中一道)
并发:
指两个或者多个事件在同一个时间段内发生(并发:交替执行)
并行:
指两个或者多个事件在同一时刻发生,同时发生(并行:同时执行)
创建线程的四种方式
1、继承Thread
类
步骤:
- 创建一个类
Thread01
,继承Thread
类- 重写
Thread
类中的run()
方法,具体逻辑代码在run()
方法中实现- 创建
Thread01
对象,并调用Thread01
父类中的start()
方法开启线程
public class Thread01 extends Thread{
private String name; // 线程名
public Thread01(String name) {
this.name = name;
}
// 重写Thread类中的run方法
@Override
public void run() {
// 线程启动后所要执行的逻辑代码
for (int i = 1; i <= 100; i++) {
System.out.println(this.name + "开启线程 = " + i);
}
}
public static void main(String[] args) {
Thread01 t1 = new Thread01("t1");
Thread01 t2 = new Thread01("t2");
t1.start(); // 开启线程
//t1.run(); // 只是调用了Thread01类中的run()方法,并未开启线程
t2.start(); // 开启线程
//t2.run(); // 只是调用了Thread01类中的run()方法,并未开启线程
}
}
为什么要重写
run()
方法?
- 因为
run()
方法是用来封装被线程所执行的代码的方法,只有重写了run()
方法才能够实现具体要执行的逻辑代码
run()
和start()
的区别:
run()
:封装线程所要执行的代码,直接调用相当于普通调用方法,并不会去开启线程start()
:是启动线程的方法,然后由JVM调用此线程中的run()
方法
Thread类本质是实现了【Runnable】接口的一个实例,代表一个线程的实例。
启动线程的唯一方法就是通过Thread
类中的start()
实例方法。
start()
方法是一个native
方法,他将启动一个新的线程,并执行run()
方法。
通过自己的类直接extends Thread
并重写run()
方法,就可以启动线程并自行自己定义的run()
方法。
优点:
代码简单
缺点:
该类无法继承别的类
2、实现Runnable
接口
步骤:
创建一个类Runnable01
,实现Runnable
接口
实现Runnable
接口中的run()
方法,具体逻辑代码在run()
方法中实现
创建Runnable01
对象,把Runnable01
对象当作参数传入到Thread
对象中
创建Thread
对象,并把Runnable01
当作参数传入,调用Thread
类中的start()
方法开启线程
public class Runnable01 implements Runnable{
private String name;
public Runnable01(String name){
this.name = name;
}
@Override
public void run() {
// 线程启动后所要执行的逻辑代码
for (int i = 0; i < 100; i++) {
System.out.println(this.name + "线程启动了 = " + i);
}
}
public static void main(String[] args) {
Runnable01 r1 = new Runnable01("r1");
Runnable01 r2 = new Runnable01("r2");
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
Java中的类属于单继承,如果自己的类已经extends
另一个类,就无法直接extends Thread
,但是一个类继承另一个类的同时 可以实现多个接口
优点:
继承其他类,统一实现该接口的实例可以共享资源
缺点:
代码复杂
3、实现Callable
接口
步骤:
创建一个类Callable01
,实现Callable<T>
接口
实现Callable<T>
接口中的call()
方法,具体逻辑代码在call()
方法中实现,执行完毕后可以返回一个值
创建Callable01
对象,把Callable01
对象当作参数传入到FutureTask<T>
对象中
创建FutureTask<T>
对象,并把Callable01
对象当作参数传入FutureTask<T>
对象中
创建Thread
对象,把FutureTask<T>
当作参数传入,调用Thread
类中的start()
方法开启线程
在通过调用FutureTask<T>
对象中的get()
方法获取线程结束后的返回值数据
public class Callable01 implements Callable<String> {
private String name;
public Callable01(String name){
this.name = name;
}
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(this.name + "约会了 " + i + "次");
}
return "约会完毕了!";
}
public static void main(String[] args) throws Exception{
Callable01 c1 = new Callable01("张三");
FutureTask<String> f1 = new FutureTask<>(c1);
Thread t1 = new Thread(f1);
t1.start();
String res = f1.get(); // get方法一定要在t1.start()之后
System.out.println("运行结果:" + res);
}
}
实现
Runnable
和实现Callable
接口的方式基本相同,不过Callable
接口中的call()
方法有返回值,Runnable
接口中的run()
方法无返回值。
注意:FutureTask<T>
的get()
方法一定要放在start()
后,否则系统会出现“死等”现象,获取不到返回值
4、线程池
方式
public class ExecutorPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "正在执行...");
});
executorService.shutdown();
ExecutorService service = Executors.newFixedThreadPool(3);
service.submit(
() -> {
System.out.println(Thread.currentThread().getName() + "正在执行...");
});
service.shutdown();
}
}
线程池是一个能容纳多个线程的容器,其中的线程可以重复使用,省去了频繁创建线程对象的操作,反复创建线程非常消耗资源。
优点:
实现自动化装配,易于管理,循环利用资源。
runnable 和 callable 的区别
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
线程有哪些状态
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
使用线程池有哪些好处
①提高系统的响应速度
②如果每次多线程操作都创建一个线程,会浪费时间和消耗系统资源,而线程池可以减少这些操作。
③可以对多个线程进行统一管理,统一调度,提高线程池的可管理性
线程池的状态有哪些
*RUNNING*:运行状态,接受新任务,持续处理任务队列里的任务。
*SHUTDOWN*:调用shutdown()方法会进入此状态,不再接受新任务,但要处理任务队列里的任务
*STOP*:调用shutdownNow()方法,不再接受新任务,不再处理任务队列里的任务,中断正在进行中的任务
*TIDYING*:表示线程池正在停止运作,中止所有任务,销毁所有工作线程。
*TERMINATED*:表示线程池已停止运作,所有工作线程已被销毁,所有任务已被清空或执行完毕。