多线程(JUC, ReentrantLock, 原子类, 线程池, 信号量 Semaphore, CountDownLatch)

简介: 多线程(JUC, ReentrantLock, 原子类, 线程池, 信号量 Semaphore, CountDownLatch)

JUC

Java.util.concurrent 包, 存放了并发编程相关的组件, 目的是更好的支持高并发任务 (多线程只是实现并发编程的一种具体方式 …)


ReentrantLock

  • synchronized 对对象加锁, 保护临界资源
  • ReentreatLock 使用 lock 方法和 unlock 方法,加锁对象是 ReentrantLock 的实例

核心方法

  • lock(): 加锁, 获取不到锁就死等
  • trylock(超时时间):尝试加锁, 如果获取不到锁, 等待一段时间后就放弃加锁
  • unlock(): 解锁

ReentrantLock 使用

由于 reentreatLock 需要手动释放, 因此推荐 try finally 的写法

// ReentrantLock 使用
public class ThreadDemo12 {
    public static void main(String[] args) {
        // true -- 公平锁       false/默认 都是非公平锁
        ReentrantLock reentrantLock = new ReentrantLock(true);
    boolean ok = reentrantLock.tryLock();
    // boolean ok = reentrantLock.lock()
        try {
            
            if (ok) {
                System.out.println("代码逻辑");
            }else {
                System.out.println("代码逻辑");
            }
        }finally {
            reentrantLock.unlock();
        }
    }

ReentrantLock 和 synchronized 比较

  1. synchronized 是关键字, 是 JVM 内部实现的
    ReentrantLock 是标准库的一个类, 在 JVM 外实现 (基于 Java 实现)
  1. synchronized 是非公平锁
    ReentrantLock 默认是非公平锁, 但是提供了公平锁版本的实现
    ReentrantLock reentrantLock = new ReentrantLock(true);
  2. ReentrantLock 提供更灵活的加锁方式:
    ReentrantLock reentrantLock = new ReentrantLock(true);
    reentrantLock.tryLock();
  3. ReentrantLock 提供更强大, 更方便的等待通知机制
    synchronized 搭配 wait() notify()使用, notify() 是随机唤醒等待队列的线程
    ReentrantLock 搭配 Condition 类. 可以唤醒指定的线程

原子类

原子类内部用的是 CAS 实现, 更高效的解决了线程安全问题

原子类提供了线程安全的自增自减等操作


原子类有以下几种 :


原子类的常见方法 (以 AtomicInteger 为例)

public class Main {
    public static void main(String[] args) {
        
        Scanner scanner = new Scanner(System.in);
        int x = scanner.nextInt();

        AtomicInteger atomicInteger = new AtomicInteger(x);
        atomicInteger.getAndIncrement();// i++;
        atomicInteger.incrementAndGet();// ++i;
        atomicInteger.getAndDecrement();// i--;
        atomicInteger.decrementAndGet();// --i;
        atomicInteger.addAndGet(x);     // i+=x;
        atomicInteger.get();            // x
    }
}

线程池

之前写过, 挂个链接这里不再复制粘贴了 — https://editor.csdn.net/md/?articleId=136715895


信号量 Semaphore

信号量表示 "可用资源的个数" .本质上是一个计数器

Semaphore 提供了 P,V 操作

P 操作: 申请一个可用资源, 计数器 - 1

V 操作: 释放一个可用资源, 计数器 + 1

当可用资源个数为 0 时, 再进行 P 操作, 就会出现阻塞等待清空 (资源为零, 无法继续消耗了), 直到有线程让信号量大于零, 才会唤醒该阻塞的线程

锁 可可以视为计数器为 1 的信号量, 二元信号量

  • 锁是信号量的一种特殊情况
  • 信号量是锁的一般表达

总结: 信号量的表达含义范围更广


Semaphore 的简单使用

代码示例

public class Main {
    public static void main(String[] args) {
        // 参数是可用资源的个数(信号量的初始值)
        Semaphore semaphore = new Semaphore(4);


        for (int i=0;i<20;i++) {
            Thread t = new Thread(() -> {
                try {
                    System.out.println("申请资源");
                    semaphore.acquire();

                    System.out.println("持有资源");
                    Thread.sleep(1000);

                    System.out.println("释放资源");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            });
            t.start();
        }
    }
}

运行结果

有兴趣可以仔细看看运行结果, 同一时刻最多只有 4个线程能够持有锁, 这就是信号量的存在意义


CountDownLatch

同时等待 N 个任务执行结束 (和 join() 功能类似)


核心API

  • await(): 阻塞等待线程, 直至任务全部完成
  • getCount(): 获取剩余未完成任务个数
  • countDown(): 未完成任务个数 -1

代码示例

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 参数代表需要等待的任务数量
        CountDownLatch countDownLatch = new CountDownLatch(5);

        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(() -> {
                System.out.println("完成一个任务");
                // countDown() 方法, 代表完成一个任务
                countDownLatch.countDown();
            });

            t.start();
            Thread.sleep(1000);
        }
        // await()方法, 用于阻塞线程
        // 直至 countDownLatch 内任务全部完成, 才会往下继续走
        countDownLatch.await();
        System.out.println("任务全部完成");
    }
}

运行结果

运行过代码会发现, 每间隔一秒输出一次 “完成一个任务”, 5秒之后输出 “任务全部完成”

目录
相关文章
|
4天前
|
存储 Linux C语言
c++进阶篇——初窥多线程(二) 基于C语言实现的多线程编写
本文介绍了C++中使用C语言的pthread库实现多线程编程。`pthread_create`用于创建新线程,`pthread_self`返回当前线程ID。示例展示了如何创建线程并打印线程ID,强调了线程同步的重要性,如使用`sleep`防止主线程提前结束导致子线程未执行完。`pthread_exit`用于线程退出,`pthread_join`用来等待并回收子线程,`pthread_detach`则分离线程。文中还提到了线程取消功能,通过`pthread_cancel`实现。这些基本操作是理解和使用C/C++多线程的关键。
|
6天前
|
安全 Java
【极客档案】Java 线程:解锁生命周期的秘密,成为多线程世界的主宰者!
【6月更文挑战第19天】Java多线程编程中,掌握线程生命周期是关键。创建线程可通过继承`Thread`或实现`Runnable`,调用`start()`使线程进入就绪状态。利用`synchronized`保证线程安全,处理阻塞状态,注意资源管理,如使用线程池优化。通过实践与总结,成为多线程编程的专家。
|
6天前
|
Java 开发者
告别单线程时代!Java 多线程入门:选继承 Thread 还是 Runnable?
【6月更文挑战第19天】在Java中,面对多任务需求时,开发者可以选择继承`Thread`或实现`Runnable`接口来创建线程。`Thread`继承直接但限制了单继承,而`Runnable`接口提供多实现的灵活性和资源共享。多线程能提升CPU利用率,适用于并发处理和提高响应速度,如在网络服务器中并发处理请求,增强程序性能。不论是选择哪种方式,都是迈向高效编程的重要一步。
|
6天前
|
Java 开发者
震惊!Java多线程的惊天秘密:你真的会创建线程吗?
【6月更文挑战第19天】Java多线程创建有两种主要方式:继承Thread类和实现Runnable接口。继承Thread限制了多重继承,适合简单场景;实现Runnable接口更灵活,可与其它继承结合,是更常见选择。了解其差异对于高效、健壮的多线程编程至关重要。
|
6天前
|
Java
JAVA多线程深度解析:线程的创建之路,你准备好了吗?
【6月更文挑战第19天】Java多线程编程提升效率,通过继承Thread或实现Runnable接口创建线程。Thread类直接继承启动简单,但限制多继承;Runnable接口实现更灵活,允许类继承其他类。示例代码展示了两种创建线程的方法。面对挑战,掌握多线程,让程序高效运行。
|
1天前
|
API C++
c++进阶篇——初窥多线程(三)cpp中的线程类
C++11引入了`std::thread`,提供对并发编程的支持,简化多线程创建并增强可移植性。`std::thread`的构造函数包括默认构造、移动构造及模板构造(支持函数、lambda和对象)。`thread::get_id()`获取线程ID,`join()`确保线程执行完成,`detach()`使线程独立,`joinable()`检查线程状态,`operator=`仅支持移动赋值。`thread::hardware_concurrency()`返回CPU核心数,可用于高效线程分配。
|
6天前
|
存储 安全 程序员
c++理论篇——初窥多线程(一) 计算机内存视角下的多线程编程
c++理论篇——初窥多线程(一) 计算机内存视角下的多线程编程
|
6天前
|
Java
你还在单线程里奋斗?来看看 Java 多线程创建的魅力!
【6月更文挑战第19天】单线程程序中任务依次执行导致效率低,而Java多线程可并行处理任务,提高效率。在示例中,多线程版本并行运行三个任务,减少总耗时,展示出多线程在处理并发和提升响应速度上的优势。使用`Thread`类创建线程,通过`start()`启动,`join()`确保所有线程执行完毕,揭示了多线程在编程中的强大潜力。
|
6天前
|
Java 开发者
线程的诞生之路:Java多线程创建方法的抉择与智慧
【6月更文挑战第19天】Java多线程编程中,开发者可选择继承Thread类或实现Runnable接口。继承Thread直接但受限于单继承,适合简单场景;实现Runnable更灵活,支持代码复用,适用于如银行转账这类需多线程处理的复杂任务。在资源管理和任务执行控制上,Runnable接口通常更优。
|
6天前
|
Java
Java 多线程新手必读:线程的创建技巧与陷阱
【6月更文挑战第19天】Java多线程初学者须知:创建线程可通过继承`Thread`或实现`Runnable`接口。继承`Thread`限制单继承,实现`Runnable`更灵活。记得调用`start()`而非`run()`启动线程,避免并发问题时需正确同步共享资源。示例代码展示两种创建方式及未同步导致的问题。