首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【javaEE】多线程--JUC的常见类

【javaEE】多线程--JUC的常见类

作者头像
那我掉的头发算什么
发布2026-01-12 18:26:35
发布2026-01-12 18:26:35
930
举报
这里是@那我掉的头发算什么 刷到我,你的博客算是养成了😁😁😁

JUC

java.util.concurrent包,这个包中又很多与多线程相关的工具

Callable

Callable是一种函数式接口,与Runnable并列,都可以表示一个任务,但是使用上略有区别。 Callable是一种有返回值的任务,可以通过泛型指定返回类型。此外,Callable接口没法直接作为参数传给Thread,需要一个中间的类FutureTask,应该是因为Thread类中没有直接提供获取返回值的方法,但是FutureTask提供了get()方法。

代码语言:javascript
复制
package Thread_11_25;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class demo37 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result = 0;
                for (int i = 0; i < 10; i++) {
                    result++;
                }
                return result;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        // get 操作就是获取到 FutureTask 的返回值. 这个返回值就来自于 Callable 的 call 方法.
        // get 可能会阻塞. 如果当前 线程 执行完毕, get 拿到返回结果.
        // 如果当前线程还没执行完毕, get 会一直阻塞.
        System.out.println(futureTask.get());
    }
}

get方法是FutureTask提供的,并非Thread提供,目的是解耦合,Thread就是执行任务,不必考虑任务是什么。 如果不使用Callable,使用Runnable当然也可以解决,但是使用时会有所不同:

代码语言:javascript
复制
package Thread_11_25;

public class demo38 {
    private static int result = 0;
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    result++;
                }
            }
        };
        Thread t = new Thread(runnable);
        t.start();
        t.join();
        System.out.println(result);
    }
}

创建线程的写法(总结)

ReenTranLock

在synchronized之前,代码普遍使用ReenTranLock锁,它是一种可重入互斥的锁。 主要方法: lock(): 加锁, 如果获取不到锁就死等. trylock(超时时间): 加锁, 如果获取不到锁, 等待⼀定的时间之后就放弃加锁. unlock(): 解锁 他在使用时需要手动加锁释放锁,所以要注意防止释放锁的代码没有执行:

代码语言:javascript
复制
package Thread_11_25;

import java.util.concurrent.locks.ReentrantLock;

public class demo39 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock locker = new ReentrantLock();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                locker.lock();
                count++;
                locker.unlock();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                locker.lock();
                try{
                    count++;
                }finally {
                    locker.unlock();
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " + count);
    }
}
在这里插入图片描述
在这里插入图片描述

Synchronized和ReenTranLock的区别

实现与定位 synchronized:Java 关键字,由 JVM 内部通过 C++ 实现(JVM 层面锁)。 ReentrantLock:Java 标准库java.util.concurrent.locks包下的类(API 层面锁)。

加解锁方式 synchronized:自动加解锁(代码块 / 方法结束自动释放),无需手动操作。 ReentrantLock:需手动调用lock()加锁、unlock()解锁(建议配合try-finally保证解锁)。

非阻塞 / 超时加锁能力 ReentrantLock:提供tryLock()(加锁失败不阻塞,返回 false)、tryLock(超时时间)(超时后自动返回)。不阻塞提高线程效率。 synchronized:无此能力,加锁失败会持续阻塞。

公平锁支持 ReentrantLock:可通过构造器指定公平锁(默认非公平),按线程等待顺序分配锁。 synchronized:仅支持非公平锁。

等待通知机制 ReentrantLock:配合Condition类,可创建多个等待队列,支持精细唤醒。 synchronized:依赖Object的wait/notify,仅一个等待队列,唤醒逻辑较粗糙。

如何选择使⽤哪个锁?

• 锁竞争不激烈的时候, 使⽤ synchronized, 效率更⾼, ⾃动释放更⽅便. • 锁竞争激烈的时候, 使⽤ ReentrantLock, 搭配 trylock 更灵活控制加锁的⾏为, ⽽不是死等. • 如果需要使⽤公平锁(根据时间先后调度), 使⽤ ReentrantLock

信号量:Semaphore

信号量, ⽤来表⽰ “可⽤资源的个数”. 本质上就是⼀个计数器.

理解信号量

可以把信号量想象成是停⻋场的展⽰牌: 当前有⻋位 100 个. 表⽰有 100 个可⽤资源. 当有⻋开进去的时候, 就相当于申请⼀个可⽤资源, 可⽤⻋位就 -1 (这个称为信号量的 P 操作) 当有⻋开出来的时候, 就相当于释放⼀个可⽤资源, 可⽤⻋位就 +1 (这个称为信号量的 V 操作) 如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

Semaphore 的 PV 操作中的加减计数器操作都是原⼦的, 可以在多线程环境下直接使⽤。

代码语言:javascript
复制
package Thread_11_25;

import java.util.concurrent.Semaphore;

public class demo40 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(3);
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
    }
}
在这里插入图片描述
在这里插入图片描述

申请三个资源,但是进行四次p操作,程序就会被阻塞。 需要先把资源释放掉才可以继续申请资源。

代码语言:javascript
复制
package Thread_11_25;

import java.util.concurrent.Semaphore;

public class demo40 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(3);
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.acquire();
        System.out.println("进行一次P操作");
        semaphore.release();
        semaphore.acquire();
        System.out.println("进行一次PV操作");
    }
}
在这里插入图片描述
在这里插入图片描述

特殊情况:当信号量为1时,取值要么是一要么是零,最多只能连续申请一次,此时和锁的效果差不多,并且PV操作都是原子的。 示例:

代码语言:javascript
复制
package Thread_11_25;

import java.util.concurrent.Semaphore;

public class demo41 {
    private static int count = 0;
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(1);

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    try {
                        semaphore.acquire();
                        count++;
                        semaphore.release();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    try {
                        semaphore.acquire();
                        count++;
                        semaphore.release();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Final count: " + count);
    }
}
在这里插入图片描述
在这里插入图片描述

CountDownLatch

有道翻译:countdown倒数,latch门闩 CountDownLatch 是 java.util.concurrent 下的同步工具类,核心作用是让一个或多个线程等待其他线程完成操作后再继续执行,基于 “计数器递减” 实现。

将一个大任务分成几个小任务,所有小任务都完成大任务也结束。

核心原理: 初始时设置计数器的值(构造方法时传参指定) 小任务完成后调用countdown,减少计数器的值 其他线程调用await,等待计数器的值为0时其他线程才可以运行,否则阻塞。

原子性的体现: countdown方法对于计数器值的修改是原子性的,支持多线程操作。

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
package Thread_11_25;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo42 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);
        ExecutorService executor = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 10; i++) {
            int id = i;
            executor.submit(()->{
                System.out.println("任务" + id + "开始运行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("任务" + id + "结束运行");
                latch.countDown();
            });
        }
        latch.await();
        System.out.println("所有任务执行完毕");

    }
}

在线程池提供的多线程环境下,程序安全的运行,直到结束。 但是此时进程并没有结束,因为线程池中仍然有前台线程处于阻塞状态。

总结

本文围绕 java.util.concurrent(JUC) 核心包,系统介绍了四大并发工具的核心逻辑与实践场景:Callable 作为带返回值的任务载体,需通过 FutureTask 适配 Thread 执行,其 get() 方法可阻塞获取结果,弥补了 Runnable 无返回值的缺陷;ReentrantLock 作为显式可重入锁,提供手动加解锁、超时加锁、公平锁及多条件等待等灵活特性,锁竞争激烈时更具优势,而锁竞争平缓时推荐使用 JVM 优化更充分的 synchronized;Semaphore 基于计数器实现资源控制,通过 PV 原子操作管理可用资源数量,支持非阻塞申请,可用于限流、资源池等场景;CountDownLatch 依靠原子性计数器实现线程同步,让一个或多个线程等待其他任务全部完成后再执行,适用于任务拆分与汇总场景。这些工具从任务封装、锁机制、资源控制、线程协作四个维度,为多线程编程提供了安全高效的解决方案,帮助开发者规避线程安全问题,提升并发程序的可控性与灵活性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 这里是@那我掉的头发算什么 刷到我,你的博客算是养成了😁😁😁
  • JUC
  • Callable
    • 创建线程的写法(总结)
  • ReenTranLock
    • Synchronized和ReenTranLock的区别
    • 如何选择使⽤哪个锁?
  • 信号量:Semaphore
    • 理解信号量
  • CountDownLatch
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档