第九章 线程

创建线程方式

  1. 继承Thread,重写run方法
class MyThread extends Thread {
    @Override
    public void run() {
        //逻辑
    }
}

// 实例化线程
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
  1. 现实Runnable接口,重写run方法
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 逻辑
    }
}

MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
  1. 创建未来任务对象,可获取返回值
// 创建未来任务对象
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        // 逻辑
        // 返回处理结果
        return 1;
    }
});

// 创建线程对象
Thread thread = new Thread(task);
thread.start();

// 获取未来任务线程返回值,此时需要等待未来任务完成才能获取返回值
Integer i = task.get();
  1. 线程池(下面详解)
// 示例
// 创建线程池对象(线程池中有3个线程)
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 将任务交给线程池
executorService.submit(new Runnable() {
    @Override
    public void run() {
        // 逻辑
    }
});
// 关闭线程池
executorService.shutdown();

线程池详解

// 官方原生线程池创建,阿里巴巴建议使用此方式创建线程池,灵活可控制
try (ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        // 核心线程数量,在线程池内创建后不销毁,一直存在
        2,
        // 总线程数量 = 核心线程数 + 非核心线程数量
        Integer.MAX_VALUE,
        // 非线程保活时间
        1,
        // 非线程保活时间单位
        TimeUnit.SECONDS,
        // 线程缓存的队列方式
        // 有界队列 可指定容量 数组
        new ArrayBlockingQueue<>(2)
        // 无界队列 链表
        // new LinkedBlockingDeque<>(2)
        // 优先任务队列,根据任务优先级顺序执行任务
        // new PriorityBlockingQueue<>(2)
        // 直接提交队列 不使用队列
        // new SynchronousQueue<>()
)) {
    // 声明 submit 提交方式的执行结果集合
    HashSet<Future<Boolean>> submitFutures = new HashSet<>();
    for (int i = 0; i < 10; i++) {
        // 执行线程任务 只能执行Runnable接口对象 无返回结果
        threadPoolExecutor.execute(() -> {
        });
        // 执行线程任务 可以执行 Callable 和 Runnable接口对象,底层执行的execute()方法
        // submit 方法执行后返回Future 接口
        Future<Boolean> submitResult = threadPoolExecutor.submit(() -> true);
        // 等待线程执行完毕
        submitResult.get();
    }
    // 关闭线程池 等待缓存队列完成,不再接受新任务
    threadPoolExecutor.shutdown();
    // 关闭线程池 丢弃缓存队列
    threadPoolExecutor.shutdownNow();
}

// 快捷方式创建线程池

/**
 * 创建固定数量线程池
 * 声明固定的核心线程数量,无非核心数量
 * return new ThreadPoolExecutor(nThreads, nThreads,
 *                                       0L, TimeUnit.MILLISECONDS,
 *                                       new LinkedBlockingQueue<Runnable>());
 */
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> {
});
/**
 * 创建调度线程池
 * 声明固定的核心线程数量,其他默认
 * return super(corePoolSize, Integer.MAX_VALUE,
 *               DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
 *               new DelayedWorkQueue());
 */
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.execute(() -> {
});
/**
 * 创建缓存线程池
 * 无核心线程,随使用创建,存活时间60S
 * return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
 *                                       60L, TimeUnit.SECONDS,
 *                                       new SynchronousQueue<Runnable>())
 */
ExecutorService executorService1 = Executors.newCachedThreadPool();
executorService1.execute(() -> {
});

/**
 * 创建1个线程的线程池
 * (new ThreadPoolExecutor(1, 1,
 *                                     0L, TimeUnit.MILLISECONDS,
 *                                     new LinkedBlockingQueue<Runnable>(),
 *                                     threadFactory));
 */
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
executorService2.execute(() -> {
});
线程池 - 等待线程执行完毕
// 线程池 CountDownLatch.await() 等待
// 不需要线程返回执行结果使用
public static void countDownLatchWait() throws Exception {
    // 创建 CountDownLatch 线程等待计数器对象
    CountDownLatch countDownLatch = new CountDownLatch(5);
    // 创建线程池
    try (ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.SECONDS, new ArrayBlockingQueue<>(5))) {
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            // execute 和 submit 都可以使用
            // threadPool.execute(() -> {
            threadPool.submit(() -> {
                try {
                    TimeUnit.SECONDS.sleep(finalI);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(finalI);
                // 每个线程执行完毕 对countDownLatch-1操作
                countDownLatch.countDown();
            });
        }
    }
    // 等待所有线程都执行完毕 countDownLatch计数器=0
    countDownLatch.await();
    System.out.println("执行完毕");
}

// 线程池 submit 提交后 使用 Future.get() 等待
// 需要线程返回执行结果使用
public static void submitFutureGetAwait() throws Exception {
    // 声明 submit 提交方式的执行结果集合
    ArrayList<Future<Boolean>> futures = new ArrayList<>();
    // 创建线程池
    try (ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.SECONDS, new ArrayBlockingQueue<>(5))) {
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            Future<Boolean> submit = threadPool.submit(() -> {
                TimeUnit.SECONDS.sleep(finalI);
                System.out.println(finalI);
                // 返回执行结果
                return true;
            });
            // 将 submitResult 放入到集合中,在循环后统一调用
            futures.add(submit);
        }
    }
    // 获取线程执行结果,此时线程阻塞,需等全部线程执行完毕后才能继续往下执行
    for (Future<Boolean> future : futures) {
        // 获取执行结果
        System.out.println(future.get());
    }
    // 运行到此处 线程已全部执行完毕
    System.out.println("执行完毕");
}

线程详解

  • 线程声明周期
1. 新建
2. 就绪
3. 运行
4. 超时等待
5. 等待
6. 阻塞
7. 终止
  • 线程让位、线程休眠、设置守护线程、设置线程名称、设置线程优先级
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        // 逻辑
        // 获取当前线程
        Thread currentThread = Thread.currentThread();
        // 获取当前线程名称 t1
        String name = currentThread.getName();
        // 获取当前线程类加载器下的资源路径
        String resource = Thread.currentThread().getContextClassLoader().getResource("").getPath();

        try {
            // 线程休眠,在声明的当前线程里起作用,单位毫秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 唤醒后会再此抛出异常
        }

        // 线程让位,会让出当前线程此次执行的时间片
        Thread.yield();

    }
});
// 设置线程名称
thread.setName("t1");

// java中线程属于抢占型cpu,设置线程优先级:1-10 默认5
thread.setPriority(Thread.MAX_PRIORITY);


// 进程分页用户线程和守护线程

// 开启守护线程,用于线程结束后,守护线程自动结束
thread.setDaemon(true);

// 启动线程
thread.start();

// 唤醒线程,线程的实例方法,利用抛出异常机制进行唤醒
thread.interrupt();
  • 定时任务
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 声明第一次执行时间
Date firstDate = simpleDateFormat.parse("2025-1-1 09:00:00");
// 定时任务
Timer timer = new Timer();
timer.schedule(new TimerTask() {
           @Override
           public void run() {
               // 逻辑
               System.out.println("o");
           }
       },
firstDate,
// 执行第一次后,以后的执行间隔
1000);
  • 线程合并
MyThread myThread = new MyThread();
myThread.start();
// 将myThread加入到此
myThread.join(1000);
// 等待myThread执行完成或时间到期后再往下执行

线程安全

线程安全同时出现:1.多线程 2.有共享数据 3.共享数据涉及修改

  1. 局部变量(在栈中,不是共享的)不存在线程安全问题。
  2. 实例变量和静态变量存在堆中,涉及到共享,存在线程安全问题。

public class Test {
    public static void main(String[] args) throws Exception {

        // 共享对象
        User user = new User(1000);

        // 线程1进行操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 对象方法
                user.withdraw(1000);
                // 静态方法
                // User.withdraw2(1000);
            }
        }).start();

        // 线程2进行操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                user.withdraw(1000);
                // 静态方法
                // User.withdraw2(1000);
            }
        }).start();
    }
}

class User {
    private int money;

    public User(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    // 在共享对象的修改方法上或方法体内 使用synchronized关键字,进行多线程同步操作
    // 共享对象方法
    //  public synchronized void withdraw(int monye) {
    public void withdraw(int monye) {
        //  synchronized(共享对象){方法体}
        synchronized (this) {
            int cuMoney = this.getMoney();
            try {
                // 模拟卡顿
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 不开启同步,多个线程会同时到此,造成多次操作,造成多次成功
            this.setMoney(cuMoney - monye);
        }
        System.out.println(this.getMoney());
    }

    // 共享静态方法
    //  public synchronized static void withdraw2(int money) {
    public static void withdraw2(int money) {
        //  synchronized(共享静态类){方法体}
        synchronized (User.class) {
            // 逻辑
        }
    }
}

// 原子性操作 java.util.concurrent.atomic 包下的类
// 示例
AtomicInteger atomicInteger = new AtomicInteger(0);
// 线程计数器
CountDownLatch countDownLatch = new CountDownLatch(3);

Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        atomicInteger.incrementAndGet();
    }
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    countDownLatch.countDown();
});

Thread thread3 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        atomicInteger.incrementAndGet();
    }
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    countDownLatch.countDown();

});
thread1.start();
thread3.start();
//        thread1.join();
//        thread3.join();
countDownLatch.await();
System.out.println(atomicInteger.get());

线程通信

  1. 需要在同步中操作
  2. 调用共享对象(父级object)的方法:wait()、notify()、notifyAll()
  3. 调用共享对象的obj.wait()后,在共享对象上活跃的所有线程进入无期限等待状态(当前线程会释放占用的对象锁,其他线程会获取共享对象锁进行执行,当前线程不唤醒就不会再去获取对象锁),直到在其他线程中调用了该共享对象的obj.notify()后进行唤醒(多个线程优先级优先,否则随机唤醒),唤醒后会接着上一次调用的wait()方法的位置继续向下执行。调用共享对象的obj.notifyAll()后唤醒所有在该共享对象上等待的线程。

可重入锁

使用Lock来实现线程安全
Lock接口下一个实现类:可重入锁ReentrantLock
让多线程共享一个Lock
Lock 比 synchronized 更加灵活,推荐使用

class User {
    // 可重入锁 静态全局共享一个Lock
    public static final ReentrantLock lock = new ReentrantLock();

    public void withdraw(int money) {
        // 加锁
        lock.lock();
        try{
            // 同步逻辑
        }finally {
            // 解锁
            lock.unlock();
        }
    }
}

虚拟线程: Java19引入

  1. 虚拟线程的核心概念
  • 轻量级实现:虚拟线程由 JVM 管理,而非操作系统内核线程。每个虚拟线程初始仅占用几百字节内存,可轻松创建数百万个,显著降低资源消耗。

  • M:N 调度模型:虚拟线程通过协作式调度(Cooperative Scheduling)实现,多个虚拟线程(M)复用少量系统线程(N)。当虚拟线程遇到 I/O 阻塞时,JVM 会自动将其从载体线程(Carrier Thread)上卸载,释放资源以执行其他任务。

  • 与平台线程的区别:

特性 传统线程(平台线程) 虚拟线程
内存占用 每个线程数百 KB 到 MB 初始仅几百字节,动态扩展
最大数量 受硬件限制(通常几千个) 轻松支持数百万个
调度方式 操作系统抢占式调度 JVM 协作式调度
适用场景 CPU 密集型任务 I/O 密集型、高并发场景
  1. 核心特性
  • 低成本创建与销毁:虚拟线程的创建和销毁成本极低,无需线程池即可处理大量短期任务。
    无阻塞 I/O 优化:虚拟线程在执行阻塞操作(如网络请求、文件读写)时,会主动释放载体线程,避免资源浪费。
  • 编程模型兼容:虚拟线程与传统线程 API 高度兼容,开发者无需学习新语法,可直接使用 Thread、ExecutorService 等现有工具。
  • 守护线程与优先级固定:虚拟线程始终为守护线程,且优先级无法通过 setPriority() 修改。
  1. 创建线程
  • 创建虚拟线程:
Runnable task = () -> {
    try {
        Thread.sleep(Duration.ofSeconds(1));
        System.out.println("Virtual Thread executed");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
};

Thread thread = Thread.ofVirtual().name("Virtual-Thread").start(task);
  • 批量任务处理:

使用 Executors.newVirtualThreadPerTaskExecutor() 为每个任务分配独立虚拟线程:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(Duration.ofMillis(100));
            return i;
        });
    });
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1、线程的概念 线程与进程 一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源,每...
    WCT的小仙女阅读 1,248评论 0 0
  • 第九章 线程 作者:Allen B. Downey 原文:Chapter 9 Threads 译者:飞龙 协议:...
    布客飞龙阅读 3,017评论 1 35
  • 线程池的实现原理和使用建议。 当提交一个新任务到线程池时,线程池的处理流程如下。1)线程池判断核心线程池里的线程是...
    巴巴11阅读 1,501评论 0 0
  • Java多线程编程实战指南 核心篇 Thread类的start方法作用是启动相应的线程。启动一个线程的实质是请求J...
    纵横Top阅读 3,365评论 0 2
  • 一、进程和线程 进程 进程就是一个执行中的程序实例,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。...
    阿敏其人阅读 7,395评论 0 13