【Java多线程】总结(完)JUC下常用类

JUC下常用类

java.util.concurrent 下的类就叫 JUC 类,JUC 下典型的类有:

ReentrantLock :可重入锁;
Semaphore :信号量;
CountDownLatch :计数器;
CyclicBarrier :循环屏障

1 可重入互斥锁ReentrantLock:

1 类似 synchronized
2 ReentrantLock 的用法:

lock(): 加锁, 如果获取不到锁就一直等;
trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁;
unlock(): 解锁。

3 ReentrantLock 和 synchronized 的区别:

  1. synchronized 是关键字 ,JVM 内部实现的(基于 C++实现).ReentrantLock 是标准库的一个类, 在 JVM 外实现的(基于 Java 实现).
  2. synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活,但是也容易遗漏 unlock
  3. synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.
  4. synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式.
  5. 更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.
2 信号量Semaphore:可以实现限流

用来表示 “可用资源的个数”,本质上就是一个计数器。该类用于控制信号量的个数,构造时传入个数。总数就是控制并发的数量。假如是5,程序执行前用acquire()方法获得信号,则可用信号变为4,程序执行完通过release()方法归还信号量,可用信号又变为5。如果可用信号为0,acquire就会造成阻塞,等待release释放信号。acquire和release方法可以不在同一个线程使用

  1. acquire:获取令牌
  2. release:发布令牌

public class SemaphoreDemo {
    // 创建信号量
    static Semaphore semaphore = new Semaphore(2);
 
    public static void main(String[] args) {
        // 创建固定线程数的线程池
        ExecutorService service = Executors.newFixedThreadPool(5);
 
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100 * i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            service.submit(() -> {
                Thread currThread = Thread.currentThread();
                System.out.println("进入线程:" + currThread.getName());
                try {
                    // 获取令牌
                    semaphore.acquire();
                    System.out.println(currThread.getName() + "得到令牌:" + LocalDateTime.now());
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(currThread.getName() + "释放令牌:" + LocalDateTime.now());
                    semaphore.release();
                }
            });
        }
        service.shutdown();
    }
}

信号量个数为2,线程1和线程2先得到令牌,信号量为0,直到线程1释放了令牌,线程3才可以得到令牌,线程2释放了令牌,线程4才能拿到令牌
信号量个数为2,线程1和线程2先得到令牌,信号量为0,直到线程1释放了令牌,线程3才可以得到令牌,

3 CountDownLatch:计数器:

判断线程池任务是否已经全部执行完

  1. countDown:计数器减一
  2. await:阻塞等待所有的任务执行完(等待CountDownLatch==0 继续之后的代码
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建计算器
        CountDownLatch countDownLatch = new CountDownLatch(5);
        // 创建线程池
        ExecutorService service = Executors.newFixedThreadPool(5);
        // 创建新线程执行任务
        for (int i = 1; i <= 5; i++) {
            service.submit(() -> {
                Thread currThread = Thread.currentThread();
                System.out.println(currThread.getName() + "开始起跑");
                int runTime = new Random().nextInt(5) + 1;
                try {
                    TimeUnit.SECONDS.sleep(runTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(currThread.getName() + "到达终点,用时:" + runTime);
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        System.out.println("比赛结果宣布!");
    }
}

在这里插入图片描述

4 CyclicBarrier 循环栅栏:线程分组的阻塞

它可以协同多个线程,让多个线程在这个屏障前等到,直到所有线程都到达了这个屏障时,再一起执行后面的操作。假如每个线程各有一个await,任何一个线程运行到await方法时就阻塞,直到最后一个线程运行到await时才同时返回。

  1. await:计数器减一,判断当前计数器是否为0,如果为0,冲破栅栏执行之后的代码,否则阻塞等待,直到冲破栅栏
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //循环屏障
        CyclicBarrier cyclicBarrier=new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("冲破循环屏障,计数器为0");
            }
        });
        ExecutorService service= Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            int finalI=i;
            service.submit(()->{
               Thread currThread=Thread.currentThread();
                System.out.println("执行线程"+currThread.getName());
                try {
                    Thread.sleep(500*finalI);
                    cyclicBarrier.await();//执行阻塞等待(计数器减一 直到循环屏障计数为0时 执行后面代码
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("线程执行完成:" + currThread.getName());
            });
        }
    }
}

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JAVA线程基本学习, JAVA多线程的特性= 线程池: 本质上是一个对象池, 用来管理线程资源. 在任务执行前, 需要从线程池中拿出线程来执行. 在任务执行成之后, 需要把线程放回线程池. 线程池好处: 降低资源的消耗, 线程本身是一种资源, 创建和销毁都会消耗CPU内存, 频繁的创建和销毁会浪内存. 提高任务执行的响应速度, 任务执行时, 可以不必等线程创建成之后在执行,可以直接获取线程执行任务. 提高线程的可管理性, 线程不能无限制的创建, 需要进行统一的分配, 调优和监控. 不适用线程池坏处: 频繁的线程创建和销毁会占用更多的CPU和内积. 频繁的创建和销毁会对gc产生比较大的压力. 线程太多,线程切换带来的开销将不可忽视. 线程太少, 多核CPU得不到充分利用, 浪费资源. 线程池实现原理: 分为三部分: 核心线程池 线程池 队列 拒绝策略 主要流程: 1.判断核心线程池是否已满, 如果不是, 则创建线程执行任务 2.如果核心线程池满了, 判断队列是否满了, 如果队列没满, 将任务放在队列中 3.如果队列满了, 则判断线程池是否已满, 如果没满, 创建线程执行任务 4.如果线程池满了, 按照拒绝策略对任务进行处理 JDK中提供了一个线程池工厂: Executors ,很多工厂方法, 可以创建多种线程池 1.单一线程池 ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); 该线程池只有一个线程, 若多个任务被提交到此线程池, 那么会被缓冲到队列,当线程空闲时,按照FIFO的方式进行处理. 2.固定数量线程池 ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5); 和创建单一线程池似, 不同的是线程池中有多个线程, 可以并行处理任务, 若多个线程任务被提交到此线程池, 会有以下执行过程: 如果线程的数量未达到指定数量, 则创建新线程执行任务. 如果线程池的数量达到了指定数量, 而且此时有线程是空闲的, 则取出空闲线程来执行任务. 如果没有线程是空闲的, 则将任务缓冲到队列, 当线程空闲的时候, 按照FIFO的方式进行处理. FIFO: 先进先出 3.带缓冲的线程池 该线程池中, 核心线程池长度为0, 线程池最大长度为Integer.MAX_VALUE.以SynchronousQueue作为等待队列, 从而每次往队列中插入一个元素, 必须等待另一个线程从这个队列删除一个元素. 定时调度 4.定时调度的线程池 ExecutorService newCachedThreadPool = Executors.newScheduledThreadPool(); ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5); ExecutorService newWorkStealingPool = Executors.newWorkStealingPool() 线程池家族 线程池的顶层接口是Executor, 这个接口定义了一个核心方法executor(Runnable command), 这个方法最后被ThreadPoolExecutor实现, 这个方法用来传入任务, 并且该是线程池的核心, 构造方法如下 : public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueu, ThreadFactory threadFactory,RejectedExecutionHandler handler); 参数意义: CorePoolSize: 核心线程池大小, 如果核心线程池有空闲的位置, 新的任务就会被核心线程池新建一个线程执行, 执行毕不会销毁线程, 线程会进入缓冲队列等待再次被运行 MaximunPoolSize: 线程池能创建最大的线程数量, 如果核心线程池和缓冲队列都已经满了, 新的任务就会进来创建新的线程来执行, 但是数量不能超过maximunPoolSize, 否则采取拒绝接受任务策略 KeepAliveTime: 非核心线程能够空闲的最长时间, 超过时间, 线程终止, 这个参数默认只有在线程数量超过核心线程池大小时, 才会起作用. Unit: 时间单位, 和keepAliveTime配合使用. WorkQueue: 缓冲队列, 用来存放等待被执行的任务 ThreadFactory: 线程工厂, 用来创建线程, 一般有三个选择 ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue Handler :拒绝处理策略, 线程数量大于最大线程数量就会拒绝处理策略, 四种策略为 ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 Executor接口有一个子接口ExecutorService, ExecutorService的实现AbstracExecutorService, 而ThreadPoolExecutor正是AbstracExecutorService的子. ThreadPoolExecutor还有两个常用的方法shutdown和submit,两者都用来关闭线程池, 但是submit有一个结果返回. 线程池任务执行 当执行executor(Runnable command)方法后, 传入一个任务, public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); } } (传入的对象实现了Runnable接口, 这就解释了: 为什么实现多线程的方式中继承Thread不能使用线程池了) 执行过程: 首先判断任务是否为空, 为空抛出空指针异常, 否则执行下一个判断: 当前线程数量是否小于核心线程池线程数量, 是,则执行addIfUbderCorePollSize(command)方法, 在核心线程池中创建新的线程, 并且执行这个任务 总结: 如果当前线程池中的数目小于corePollSize, 则每来一个任务, 就会创建一个新的线程去执行这个任务 如果当前线程池中的线程数目>=corePollSize, 则每来一个任务, 会尝试将其添加到缓冲队列中, 如果添加成功, 则该任务会等待空闲线程将其取出去执行, 如果添加失败(一般是以为任务队列已经满了), 则会尝试创建新的线程去执行这个任务. 如果当前线程池中的线程数目达到maximunPoolSize, 则会采取任务拒绝策略进行处理. 如果线程池中的线程数量大于corePoolSize时, 如果某线程空闲时间超过keepAliveTime, 线程将被终止, 直至线程池中的线程数目小于等于corePoolSize; 如果允许核心池中的线程设置存活时间, 那么核心池中的线程空闲时间超过keepAliveTime, 线程也会被终止.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值