多线程
2024-06-15 本文已影响0人
星汉西流_夜未央
线程
- 线程是操作系统能够运行调度的最小单位, 它被包含在进程之中, 是进程的实际运作单位
- 简单来说, 线程就是应用软件可以同时运行, 互相独立的功能
进程
- 进程是程序的基本执行实体, 一个应用软件在运行的过程中可以看作是一个进程
多线程及其应用场景
多线程就是系统同时执行多个线程的操作, 可以提高工作效率
- 软件中的耗时操作: 例如拷贝迁移大规模文件, 加载大量资源文件
- 所有的聊天软件, 所有的后台服务器, 都需要运用多线程操作
并发和并行
- 并发: 在同一时刻, 有多个指令在单个CPU上交替执行
- 并行: 在同一时刻, 有多个指令在单个CPU上同时执行
多线程的实现方式
- 继承Thread类, 重写run方法
public class MyThread extends Thread{
@Override
public void run(){
//书写线程需要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "HelloWord");
}
}
}
测试类中的测试代码
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
//线程1和线程2的run方法会交替进行
- 实现Runnable接口, 重写run方法, 要将该类的对象传递到Thread类的对象中
public class MyRun implements Runnable{
@Override
public void run() {
//由于没有继承Thread类, 无法直接使用getName方法
//可以使用Thread类中的静态方法currentThread, 该方法由哪个线程调用, 就返回该线程的对象
Thread t = Thread.currentThread();
for (int i = 0; i < 100; i++) {
System.out.println(t.getName() + "HelloWorld");
}
}
}
//创建MyRun对象, 该对象就表示后续线程要完成的任务
MyRun mr = new MyRun();
//创建Thread对象, 并将mr传递过去
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
//方便看清结果,设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
//部分结果
//线程2HelloWorld
//线程1HelloWorld
//线程2HelloWorld
//线程1HelloWorld
//线程2HelloWorld
//线程1HelloWorld
- 利用Callable接口和Future接口方式实现, 重写call方法
public class MyCallable implements Callable<Integer> {
//Callable接口为泛型接口, call方法的返回值即为线程运行完之后的结果
//返回值的类型就是实现Callable接口时所给定的类型
@Override
public Integer call() throws Exception {
//计算1~100的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
//创建MyCallable对象, 表示线程要完成的任务
MyCallable mc = new MyCallable();
//创建Future接口的对象, 用于管理多线程的结果
//由于接口无法直接创建对象, 所以我们需要创建Future实现类FutureTask的对象
//并将任务对象传递过去
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建Thread的对象, 并将FutureTask对象传递过去
Thread t = new Thread(ft);
//启动线程
t.start();
//获取线程的结果
Integer result = ft.get();
System.out.println(result);//5050

- 只有第三种实现方法可以获取线程运行后的结果
多线程常见的成员方法

- 线程的优先级相关方法
//没有设置优先级的情况下, 线程的优先级默认为5
//优先级越高, 表示该线程被执行的概率越高, 但不代表一定最先完成该线程
Thread t = Thread.currentThread();
System.out.println(t.getPriority());//5
System.out.println(t1.getPriority());//5
System.out.println(t2.getPriority());//5
t1.setPriority(1);
t2.setPriority(10);
//可以设置线程的优先级
- 守护线程: 当其他非守护线程执行完毕后, 守护线程也会陆续结束, 即使没执行完
//创建两个任务对象
MyThread t1 = new MyThread();
MyThread1 t2 = new MyThread1();
t1.setName("云想衣裳花想容");
t2.setName("星汉西流夜未央");
//设置t2为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
- 出让线程: 让线程执行尽可能均匀, 但并不绝对, 即一个线程如果写了出让线程的方法, 那么它执行一次后, 将出让CUP执行权, 重新去争夺
- 插入线程: 谁调用该join方法, 就表明要把该线程插入到当前线程(main)之前, 即该线程执行完毕, 才会执行当前线程
线程的生命周期

线程的安全问题
- 当进行多线程操作时, 由于线程的随机性, 可能会出现线程的不安全, 即多线程对同一个数据的处理可能会导致出现一些超出预期的问题, 这时我们就需要对线程加锁(同步代码块)
- 同步代码块: 把操作共享数据的代码锁起来
- 特点1: 锁默认打开, 当有一个线程进去了, 锁自动关闭
- 特点2: 里面的代码全部执行完毕, 线程出来, 锁再次打开
synchronized (锁对象){
} //锁对象是任意的, 但必须是唯一的, 通常写当前类的字节码文件, 例如MyThread.class
- 同步方法: 就是把synchronized关键字加到方法的修饰符后面
- 特点1: 同步方法是锁住方法里面所有的代码
- 特点2: 锁对象不能自己指定(非静态方法为this, 静态方法为当前类的字节码文件对象)
对于锁
- Java中有一个Lock接口, 可以采用其实现类ReentrantLock创建对象, 实现手动打开和关闭锁
生产者和消费者(等待唤醒机制)
- 生产者和消费者是一个十分经典的多线程协作的模式, 即生产者生产了, 消费者再消费, 依次有序进行
线程的完整状态

实际Java中线程只有6种状态, 没有定义运行状态