Java多线程

Java多线程

Java多线程学习路线

学习阶段 学习内容
初级 多线程的概念、线程的生命周期、线程的状态
继承 Thread 类创建线程、实现 Runnable 接口创建线程
线程同步、线程通信、死锁
中级 线程池的概念和使用
线程安全的集合类、原子类
synchronized 关键字、ReentrantLock 类的使用
Condition 类的使用、ReadWriteLock 的使用
高级 并发编程工具类的使用、volatile 关键字的使用
原子类的使用、ThreadLocal 的使用
并发编程的设计模式、Java 并发编程的性能优化

Java多线程介绍

Java多线程可以用于处理复杂的任务,例如网络编程、GUI编程和并发编程等。

  • 在网络编程中,一个Java应用程序可以同时处理多个客户端请求;
  • 在GUI编程中,Java多线程可以使应用程序保持响应,并且可以在后台执行长时间运行的任务;
  • 在并发编程中,Java多线程可以实现多个线程的并发执行,从而提高应用程序的性能和响应速度。

每个Java程序都至少有三个线程——main主线程gc()垃圾回收机制的运行线程异常处理线程

Java中的多线程是通过Thread类和Runnable接口来实现的。
Thread类是Java中处理多线程的核心类,它提供了多种线程相关的方法,包括创建线程、启动线程、中断线程等。一个Java应用程序可以同时创建多个线程,每个线程可以执行不同的任务。
Runnable接口是Java多线程的另一个重要接口,它定义了线程的执行代码。一个线程可以通过实现Runnable接口来定义自己的执行代码,然后通过Thread类的构造方法将其包装为一个线程对象。

Java多线程的优点:

  • 提高程序性能和效率
  • 可以同时处理多个任务
  • 使应用程序保持响应,增加用户体验
  • 支持并发编程,实现多个线程的并发执行
  • 可以通过同步机制避免竞态条件和死锁等问题

Java多线程的缺点:

  • 多线程编程难度较高,需要掌握一些复杂的概念和技术
  • 多线程会占用更多的系统资源,对系统的负担会更大
  • 多线程容易导致竞态条件、死锁等问题,需要谨慎处理

Java多线程的注意事项:

  • 主线程任务结束后并且子线程任务没有结束,则主线程退出子线程继续执行

并发与并行

  • 并发(concurrent)是同一时间应对(dealing with)多件事情的能力;
  • 并行(parallel)是同一时间动手做(doing)多件事情的能力;

简单案例

创建一个线程有两种方法,一种是继承Thread类,另一种是实现Runnable接口

继承Thread类案例
public class MyThread extends Thread {
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程正在运行: " + i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            System.out.println("子线程已完成");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();

        try {
            t.join();
        } catch (InterruptedException e) {
            System.out.println("主线程中断");
        }

        System.out.println("主线程已完成");
    }
}
实现Runnable接口案例
public class Test {
    public static void main(String[] args) {
        // 创建一个Runnable实例,代表一个任务
        Runnable task = new Runnable()//匿名内部类
        {
            //该对象的run方法会单独在一个线程中被调用
            @Override
            public void run() {
                // 执行任务  
                System.out.println("子线程执行任务");
            }
        };

        // 创建一个线程
        Thread thread = new Thread(task);
        // 启动线程
        thread.start();

        // 主线程继续执行其他任务
        System.out.println("主线程继续执行其他任务");
    }
}

实现原理

start()方法最终调用的是start0()
start0()是真正实现多线程的方法

线程 生命周期中的六种状态

等待状态和阻塞状态在一些文献中会合并为一种状态

线程基础状态 描述
new 新创建的线程,但未启动
runnable 正在JVM中运行的线程
blocked 线程阻塞于锁
waiting 等待另一个线程的动作
timed_waiting 在指定的时间内等待另一个线程的动作
terminated 已退出线程的生命周期

线程组合状态 描述
new -> runnable 线程调用start()方法后进入此状态
runnable -> terminated 线程执行完run()方法后进入此状态
runnable -> blocked 线程在竞争锁的过程中进入此状态
runnable -> waiting 线程调用wait()方法后进入此状态
runnable -> timed_waiting 线程调用sleep()、join()或者wait(timeout)方法后进入此状态
blocked -> runnable 线程获取到锁后进入此状态
waiting -> runnable 线程被notify()、notifyAll()方法唤醒后进入此状态
timed_waiting -> runnable 等待时间到达后进入此状态
状态 描述 状态转换
新建状态(new) 创建了线程对象,但还没有调用start()方法启动线程 调用start()方法启动线程
就绪状态(runnable) 线程已经启动,但是调度程序还没有选择它来执行 调用start()方法后,线程进入就绪状态;当等待锁或者等待输入输出操作完成时,线程会进入阻塞状态
运行状态(blocked) 调度程序已经选择了线程,并且线程正在执行中 线程进入运行状态后,可以调用sleep()、yield()、wait()等方法来暂停执行,使线程进入阻塞状态或等待状态;当线程执行完run()方法,或者因为异常退出run()方法时,线程会进入终止状态
阻塞状态(waiting) 线程暂时停止执行,等待某个条件的满足,例如等待I/O操作的完成或等待获取对象的锁 当等待的条件被满足时,线程会重新进入就绪状态;当调用sleep()、yield()、wait()等方法后,线程会进入等待状态;当等待时间到达或者被调用notify()、notifyAll()方法唤醒时,线程会重新进入就绪状态
等待状态(timed_waiting) 线程等待其他线程发出通知或者指定时间到达,进入等待池 当等待的条件被满足时,线程会进入就绪状态;当等待时间到达时,线程会重新进入就绪状态;当被调用notify()、notifyAll()方法唤醒时,线程会重新进入就绪状态
终止状态(terminated) 线程已经执行完run()方法或者因为异常退出run()方法,不再具有继续执行的能力 线程进入终止状态后,不可以再次启动,否则会抛出IllegalThreadStateException异常

Java多线程常用类

类名 描述
Thread 线程类,用于创建和管理线程。
Runnable 线程执行的任务接口。
ThreadGroup 线程组,用于管理一组线程。
Executors 线程池工具类,用于创建和管理线程池。
Lock 互斥锁,用于控制对共享资源的访问。
ReentrantLock 可重入锁,Lock接口的实现类。
Condition 条件对象,用于线程之间的协调。
Semaphore 信号量,用于控制对共享资源的访问。
CountDownLatch 倒计时锁,用于等待一组线程执行完毕后再执行主线程。
CyclicBarrier 循环屏障,用于等待一组线程到达某个状态后再一起执行。
ExecutorService 线程池接口,用于管理线程池。
ScheduledExecutorService 定时执行线程池接口,用于定时执行任务。
Future 异步任务的结果接口,可以用于获取异步任务的结果。

Java多线程API

方法 描述
synchronized 保证同一时刻只有一个线程可以执行某个方法或代码块
volatile 保证变量的可见性,即一个线程修改了该变量的值,其他线程立即可以看到修改后的值
wait() 让线程等待
notify() 唤醒等待的线程
notifyAll() 唤醒所有等待的线程
join() 等待线程执行结束
yield() 让出CPU时间
setDaemon() 设置线程为守护线程
isAlive() 判断线程是否还存活
interrupt() 中断线程
isInterrupted() 判断线程是否被中断
start() 启动线程
run() 线程执行体
sleep() 让当前线程休眠指定时间
getPriority() 获取线程优先级
setPriority() 设置线程优先级
getName() 获取线程名字
setName() 设置线程名字
getId() 获取线程ID

线程通信

sleep()和wait()的区别

  • 一旦使用,均可使当前线程进入阻塞状态;
  • 声明位置不同:sleep()声明在Thread类中,wait()声明在Object类中;
  • 调用要求不同:sleep()可以使用在各种需要的地方,而wait()只能用在同步代码块或同步方法里;
  • sleep()使用不释放锁,而wait()使用后会释放锁。
  • 在多线程中底层的静态代理:

    在Java中,当我们想要启动一个新的线程时,我们需要创建一个Thread对象,并调用其start方法来启动线程。实际上,在Thread类的内部,start方法是通过JNI调用底层操作系统提供的线程实现来启动一个新的线程。

在这个过程中,Thread类可以被看作是用户与底层线程实现之间的代理。用户通过Thread类来创建和控制线程,而底层线程实现则负责实际的线程执行过程。这种代理模式就是静态代理模式。

具体来说,Thread类提供了一系列控制线程执行的方法,如sleep、join等方法,使得用户可以方便地控制线程的执行过程。而底层线程实现则负责实际的线程执行过程,包括线程的创建、销毁、调度等过程。

在调用Thread类的start方法时,实际上是在使用静态代理模式,将用户提交的任务委托给底层线程实现,并启动一个新的线程来执行任务。这样,通过代理的方式,将线程的创建与销毁过程与线程的执行过程分离开来,使得线程的实现更加灵活、可控,更加符合用户的需求。