java多线程学习笔记2-callable接口,多线程常用方法,读写锁,阻塞队列,线程池

java多线程学习笔记2

三.Callable接口

1.与runnable接口的对比
callable接口有返回值
callable接口抛异常
callable接口的实现方法是call() ,runnable接口的实现方法是run()

2.Runnable接口使用
无法直接使用Thread()构造器 创建 因为Thread() 的构造器中需填 Runnable接口的实现类

使用FutureTask类 FutureTask类实现类Runnable接口。

FutureTask futureTask = new FutureTask(new Callable() {
          @Override
          public Object call() throws Exception {
              return null;
          }
      });
new Thread(futureTask,"thread1").start();
new Thread(futureTask,"thread2").start();
try {
    futureTask.get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

即使创建多个线程 多次调用futuretask的get方法futureTask也只计算一次;
除了第一次 后面的都会get保存的的参数的数据(有一个state状态 判断是否得到过数据)
如果想多次计算,应创建多个FutureTask的实例。

四.JUC提供的几个常用方法

1.CountDownLatch 减少计数

CountDownLatch主要有两个方法 当多个线程调用await方法时,这些线程会阻塞。当其他线程调用countDown方法 会将计数器减1(调用countDown()方法的线程不会阻塞),当计数器降为0时。因await()方法阻塞的所有线程会被唤醒。

/**
     * 学生回家问题 全部5个学生回家才锁门
     * @throws InterruptedException
     */
    @Test
    public void test1() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);

        for (int i=0; i<5; i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"回家了");
                countDownLatch.countDown();
            },"线程"+i).start();

        }
        countDownLatch.await();;
        System.out.println("五个学生全部回家 锁门!");
    }
2.CyclicBarrier 循环栅栏

字面意思是可循环使用的屏障。
让多个线程达到一个屏障(也叫做同步点)时被阻塞,知道最后一个线程到达屏障时在后开门唤醒全部线程,线程进入线程通过CyclicBarrier的await()方法

/**
     * 6个修理工一起修理机械 修理完6个修理工一起回家
     */
    @Test
    public void test2(){
        CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
            System.out.println("修理完毕 可以回家了");//线程执行在够数量的线程执行到await(),放开栅栏前
        });

        for (int i=0; i<6; i++){
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"修理完毕");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName()+"回家");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },"线程"+i).start();

        }
    }

countDownLatch和CyclicBarrier的区别
countDownLatch是wait()在主线程 等待所有分支线程执行完,CyclicBarrier是await在多线程中等待够数量的线程执行完一起执行之后的任务。

3.semaphore 信号灯

在信号量上我们定义两种操作
acpuire (获取)当一个线程获取信号时,要么成功获取信号,信号量减1,要么一直等待知道有空闲信号或超时
release(释放)信号量的值加一,唤醒等待的线程
信号量主要用于两个目的,一 多个共享资源互斥使用。 二 线程并发数的控制

/**
     * 银行取钱问题10人取钱 3个柜台
     */
    @Test
    public void test3 (){
        Semaphore semaphore = new Semaphore(3);
        for (int i=0; i<10; i++){
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"来到柜台取钱");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName()+"离开柜台");
                    semaphore.release();
                }

            },"线程"+i).start();

        }
    }

五.读写锁 ReentrantReadWriteLock

ReentrantReadWriteLock只有两个方法 readLock();writeLock();返回一个读锁或写锁,然后调用lock()或unlock()方法加锁解锁

读写锁的特征:
允许多个线程同时读共享变量;
只允许一个线程写共享变量;
如果有一个线程在执行写操作,那么禁止读操作。

六.BlockingQueue阻塞队列

当队列是空的,在队列中取元素会阻塞
当队列是满的,往队列中放元素会阻塞

试图在空的队列中取元素的线程会阻塞,直到其他线程往空的队列中放入新的元素
试图往满的队列中放元素的队列会阻塞,直到其他线程将队列中的元素取走,队列有空位

阻塞队列的用处

在这里插入图片描述

种类分类:

在这里插入图片描述

BlockingQueue核心方法
在这里插入图片描述

七.线程池

1.为什么使用线程池

曾经的单核电脑是假的多线程 实际上是一个cpu来回切换处理多个线程
现在多核电脑可以真正的多个线程跑在各自的cpu上,不用切换,效率高

2.线程池的优势

线程池做的工作只是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果任务数量超过最大线程数,则超出的任务将在队列中排队,等待其他线程执行完毕,在在队列中取出执行

优点:线程复用,控制最大并发,统一管理线程

降低资源的消耗:通过重复利用已创建的线程,降低创建销毁线程造成的损耗

提高响应速度:任务到达后,任务无需等待线程创建,就可以直接执行

提高线程管理性:线程是稀缺资源,无限制新建不仅会消耗系统资源还会降低系统稳定性,使用线程池可对线程统一分配,调优,监控

3.系统的几个默认线程池

在这里插入图片描述

//newFixedThreadPool的corePoolSize和maxinumPoolSize数量相等 
//阻塞队列是lickedBlockingQueue 大小Integer.MAX_VALUE(可能堆积大量请求导致OOM)
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
//newSingleThreadExecutor newFixedThreadPool的corePoolSize和maxinumPoolSize数量为1 
//但是阻塞队列是lickedBlockingQueue 大小Integer.MAX_VALUE(可能堆积大量请求导致OOM)
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
//newSingleThreadExecutor newFixedThreadPool为0  corePoolSize和maxinumPoolSize为Integer.MAX_VALUE(可能创建太多线程导致OOM)
//SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素
//意思是任务来了就创建线程,空闲60秒后销毁线程
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
4.线程池有7个参数:

在这里插入图片描述

5.什么是拒绝策略

当线程池的线程全部被占用 并且等待队列满了 ,塞不下新的任务,这是就要使用拒绝策略

在这里插入图片描述
以上拒绝策略均实现了RejectedExecutionHandler接口

一般我们应自己手动创建线程池
创建自定义线程池:

        ExecutorService executorService = new ThreadPoolExecutor(3,
                10,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
6.线程池运行原理

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值