多线程(一) ---- Java

本文详细介绍了Java中的多线程概念,包括进程与线程的区别、并发与并行、为何使用多线程,以及Java线程与操作系统线程的关系。接着讲解了线程的生命周期、创建线程的多种方式,如继承Thread、实现Runnable和Callable接口,并对比了它们的差异。还讨论了线程调度、线程安全和避免死锁的方法,包括synchronized关键字、Lock锁的使用以及wait和notify机制。
摘要由CSDN通过智能技术生成

线程概述

1. 进程 和 线程

  • 进程:在一个操作系统中,每个独立执行的程序都可以称之为一个进程,也就是 " 正在运行的程序 "
  • 线程:一个进程中可以有多个执行单元同时运行,来同时完成一个或多个程序任务,这些执行单元可以看作程序执行的一条条线索,被称为线程。操作系统中的每一个进程都至少存在一个线程
  • 多线程:一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式

请添加图片描述

2. 进程 和 线程 的区别

  • 进程是包含线程的,每个进程至少有一个线程存在,即主线程
  • 进程和进程之间不共享内存空间, 同一个进程的线程之间共享同一个内存空间
  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位

3. 并行 和 并发

  • 并发:微观上一个 CPU 核心,先执行一会任务A,再执行一会任务B,任务A和任务B交替进行,只要切换的够快,宏观上看来就像多个任务同时执行
  • 并行:微观上两个 CPU 核心,同时执行任务A和任务B

4. 为什么使用多线程

  1. 为了更好的利用cpu的资源,若只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待
  2. 进程之间不能共享数据,线程可以
  3. 线程比进程更轻量:创建线程比创建进程更快;销毁线程比销毁进程更快 ;调度线程比调度进程更快

5. Java 的线程 和 操作系统线程 的关系

  • 线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用
  • Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装

线程的生命周期

  • 新建状态:
    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态,此时不能运行,仅仅由 JVM 为其分配了内存
  • 就绪状态:
    当线程对象调用了 start() 方法之后,该线程就进入状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度
  • 运行状态:
    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态:
    处于运行状态的线程可能会因为某些原因失去 CPU 的执行权,暂时停止运行进入阻塞状态,此时, JVM 不会给线程分配 CPU ,直到线程重新进入就绪状态。阻塞状态的线程只能先进入就绪状态,不能直接进入运行状态

一般在两种情况下进入阻塞状态:

  1. 当线程 A 运行过程中试图获取同步锁时,却被线程 B 获取,此时 JVM 会把当前线程 A 存到对象的锁池中,线程 A 进入阻塞状态
  2. 当线程运行过程中,发出 I/O 请求时,该线程也会进入阻塞状态
  • 等待状态
    当处于运行状态的线程调用了无时间参数限制的方法后,例如:wait()、jion() 等方法,就会将当前运行中的线程转变为等待状态

处于等待状态中的线程不能立即争夺 CPU 使用权,必须等待其他线程执行特定的操作后,才有机会将等待状态转换为运行状态
例如:等待其他线程调用 notify() 或者 notifyAll() 方法唤醒当前等待中的线程;调用 jion() 方法而处于等待状态中的线程,必须等待其他加入的线程终止

  • 定时等待状态
    和等待状态类似,只是运行线程调用了有时间参数限制的方法,例如:sleep(long millis)、wait(long timeout)、jion(long millis)等方法

处于定时等待状态中的线程也不能立即争夺 CPU 使用权,必须等待其他相关线程执行完特定的操作或者限制的时间后,才有机会将等待状态转换为运行状态
例如:通过其他线程调用 notify() 或者 notifyAll() 方法唤醒当前等待中的线程,或者等待限时时间结束后也可以进行状态转换

  • 终止状态:
    线程的 run() 方法、call() 方法正常执行完毕或者线程抛出一个未捕获的异常、错误,线程就会进入终止状态,一旦进入终止状态,线程将不再拥有运行的资格,也不能再转换为其他状态,生命周期结束

线程的创建

1. 继承 Thread 类,重写 run() 方法

步骤:

  1. 创建一个 Thread 线程类的子类(子线程),同时重写 Thread 类的 run() 方法
class MyThread extends Thread {
   
    @Override
    public void run() {
   
        System.out.println("这里是线程运行的代码");
   }
}
  1. 创建该子类的实例对象,并通过调用 start() 方法启动线程
MyThread t = new MyThread();
t.start(); // 线程开始运行

代码实现:

class myThread extends Thread {
   
    public void run() {
   
        for (int x = 0; x < 10; x++) {
   
            //currentThread(): Thread 类的静态方法,用来获取当前线程对象
            //getName(): 用来获取线程名称  
            System.out.println(Thread.currentThread().getName()+ "运行");
        }
    }
}

public class duoxiancheng {
   
    public static void main(String[] args) {
   
        myThread t1 = new myThread();
        t1.start();
        myThread t2 = new myThread();
        t2.start();
        for(int x = 0; x < 10; x++){
   
            System.out.println("main:"+x);
        }
    }
}

请添加图片描述

main() 方法中有一条主线程在运行

2. 实现 Runnable 接口,重写 run() 方法

Java 只支持类的单继承,如果某个类已经继承了其他父类,就无法再继承 Thread 类来实现多继承,此时,可以通过实现 Runnable 接口来实现多线程

步骤:

  1. 创建一个 Runnable 接口的实现类,同时重写接口中的 run() 方法
class myThread implements Runnable{
   
    @Override
    public void run() {
   
        System.out.println("这里是线程运行的代码");
        }
    }
}
  1. 创建 Runnable 接口的实现类对象,使用 Thread 有参构造方法创建线程实例,并将 Runnable 接口的实现类的实例对象作为参数传入
//创建 Runnable 接口的实现类对象
myThread1 t1 = new myThread1();
//使用 Thread(Runnable target,String name) 构造方法创建线程对象
Thread t2 = new Thread(t1,"t2");
//调用线程对象的 start() 方法启动线程
t2.start();

Thread 的常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名

代码实现:

class myThread1 implements Runnable{
   
    @Override
    public void run() {
   
        for (int x = 0; x < 10; x++) {
   
            //currentThread(): Thread 类的静态方法,用来获取当前线程对象
            //getName(): 用来获取线程名称
            System.out.println(Thread.currentThread().getName()+ " 运行");
        }
    }
}

public class duoxiancheng {
   
    public static void main(String[] args) {
   
        myThread1 t = new myThread1();
        Thread t2 = new Thread(t,"顺顺");
        t2.start();
        //创建并启动另一个线程
        Thread t3 = new Thread(t,"利利");
        t3.start();
    }
}

请添加图片描述

其他实现:

//1:将 Runnable 实例传给 Thread
    public static void main(String[] args) {
   
        Thread t = new Thread(new myThread1());
        t.start();
    }
//2:创建匿名内部类
    public static void main(String[] args) {
   
        Thread t = new Thread(){
   
            @Override
            public void run() {
   
                for (int x = 0; x < 10; x++) {
   
                    System.out.println(Thread.currentThread().getName()+ "运行");
                }
            }
        };
        t.start();
    }   
//3:使用匿名类创建 Runnable 子类对象
    public static void main(String[] args) {
   
        Thread t = new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                for (int x = 0; x < 10; x++) {
   
                    System.out.println(Thread.currentThread().getName()+ "运行");
                }
            }
        });
        t.start();
    } 
//4:lambda 表达式
    public static void main(String[] args) {
   
        Thread t = new Thread(() -> {
   
            for (int x = 0; x < 10; x++) {
   
                System.out.println(Thread.currentThread().getName()+ "运行");
            }
        });
        t.start();
    }

3. 实现 Callable 接口,重写 call() 方法,并使用 Future 来获取 call() 方法的返回结果

Thread 类和 Runnable 接口实现多线程时需要重写 run() 方法,但 run() 方法没有返回值,因此无法从多个线程中获取返回结果,此时,可以使用 Callable 接口来满足既创建多线程又有返回值的需求

步骤:

  1. 创建一个 Callable 接口的实现类,同时重写 call() 方法
  2. 创建 Callable 接口的实现类对象
  3. 通过 FutureTask 线程结果处理类的有参构造方法封装 Callable 接口实现类对象
  4. 使用参数为 FutureTask 类对象的 Thread 有参构造方法创建 Thread 线程实例
  5. 调用线程实例的 Start() 方法启动线程

代码实现:

class myThread2 implements Callable<Object>{
   
    //重写 call() 方法
    @Override
    public Object call() throws Exception {
   
        int x = 0;
        for (; x < 10; x++) {
   
            System.out.println(Thread.currentThread().getName()+ "运行");
        }
        return x;
    }
}

public class duoxiancheng {
   
    public static void main(String[] args) throws ExecutionException, InterruptedException {
   
        //创建 Callable 接口的实现类对象
        myThread2 t1 = new myThread2();
        //通过 FutureTask 封装 Callable 接口实现类对象
        FutureTask<Object> f1 = new FutureTask<>(t1);
        //使用 Thread 有参构造方法创建线程对象
        Thread t11 = new Thread(f1,"ABC");
        t11.start();

        FutureTask<Object> f2 = new FutureTask<>(t1);
        Thread t22 = new Thread(f2,"DEF");
        t22.start();

        System.out.println("t11 返回结果:" + f1.get());
        System.out.println("t22 返回结果:" + f2.get());
    }
}

请添加图片描述

  1. Callable 和 Runnable 相对, 都是描述一个 “任务”。 Callable 描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务
  2. Callable 通常需要搭配 FutureTask 来使用,FutureTask 用来保存 Callable 的返回结果。因为Callable 往往是在另一个线程中执行的, 什么时候执行完并不确定,FutureTask 就可以负责这个等待结果出来的工作

4. 三种方式对比

三种方式中,Runnable 接口和 Callable 接口 实现多线程的方式基本相同,主要区别是 Callable 接口方法中有返回值,并且可以声明抛异常

Thread 和 Runnable 的区别:

  1. Runnable 接口(或 Callable 接口)比起 Thread 类 来说,更适合多个线程处理同一个共享资源的情况
  2. Runnable 接口(或 Callable 接口)可以避免 Java 单继承带来的局限性
class Ticket extends Thread{
   
    private int tickets = 20;

    @Override
    public void run() {
   
        while(true)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值