【JUC 二】Callable Runnable 同步辅助类 读写锁 阻塞队列 线程池

7、Callable()

7.1、Callable 与 Runnable

比较CallableRunnable
类型接口 (函数式接口,可用lambda)接口 (函数式接口,可用lambda)
java.util.concurrentjava.lang
方法call()run()
返回值有返回值。类型与泛型的类型一致没有返回值。void
导常可以抛出异常不能抛出被检查的异常

7.2、Thread 中使用 Callable

问题

  • Thread 不能直接使用 Callable 的实现类

    Thread 构造器中不能接受 Callable 的实现类,但可以接受 Runnable 的实现类

思路

让 Callable 和 Runnable 建立联系,通过 Runnable 做桥梁,让 Thread 可以使用 Callable

8、同步辅助类 (必会)

8.1、CountDownLatch

同步计数器(减法)

原理:

countDownLatch.countDown(); //数量减1

countDownLatch.await();// 等待计数器归零,然后再向下执行

每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await(),就会被唤醒,继续执行

  • 示例

厂区门卫

当所有人都离开后,再关门

package auxiliaryClass;

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

/**
 * @author ajun
 * Date 2021/7/5
 * @version 1.0
 * 减法计算器
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        //定义计算器
        // 倒计时总数是6, 必须要执行任务的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(new FutureTask<>(() -> {
                System.out.println(Thread.currentThread().getName() + " : 走了!");
                countDownLatch.countDown();//计算器减1
                return 1000;
            }), String.valueOf(i)).start();
        }
        countDownLatch.await();// 等待计数器归零,然后再向下执行
        System.out.println("关门!");
    }
}

8.2、CyclicBarrier

同步计数器(加法)

cyclic 循环

barrier 栅栏

  • 示例

观光车:每达到5人时发车

package auxiliaryClass;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

/**
 * @author ajun
 * Date 2021/7/5
 * @version 1.0
 * 循环栅栏
 * 观光车:每达到5人时发车
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) throws InterruptedException {
        //观光车:每达到5人时发车
        CyclicBarrier bus = new CyclicBarrier(5,() -> {System.out.println("可以发车了");});

        for (int i = 1; i <= 50; i++) {
            TimeUnit.SECONDS.sleep(1);//乘客到来的时间间隔
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 号乘客来了");
                try {
                    bus.await();//等待。达到5人就通知发车,否则就等待。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

8.3、Semaphore

信号量、信号灯

原理:

semaphore.acquire(); //获取信号量,假设已经满了,等待信号量可用时被唤醒

semaphore.release();//释放信号量

作用:

多个共享资源互斥的使用!

并发限流,控制最大的线程数

  • 示例

    停车场

    10个位,30辆车

package auxiliaryClass;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author ajun
 * Date 2021/7/5
 * @version 1.0
 * 信号灯:抢车位
 */
public class SemaphoreTest {
    private static final int MAX_PARKING = 10;//最大停车位
    private static int cars = 0;//当前停车数量

    public static void main(String[] args) throws InterruptedException {
        final Semaphore semaphore = new Semaphore(MAX_PARKING);//停车场
        Random random = new Random();//计算随机停车时间
        for (int i = 1; i <= 30; i++) {//30辆车
            TimeUnit.SECONDS.sleep(1);//车辆进场间隔
            int t = 10 + random.nextInt(5);//随机停车时间 10 - 15
            new Thread(() -> {
                try {
                    semaphore.acquire();//获得车位。如果没有车位,就阻塞等待。
                    System.out.println(Thread.currentThread().getName() + "号车进场!");
                    carsNum(1);//车辆数加1
                    TimeUnit.SECONDS.sleep(t);//停车时间
                    System.out.println(Thread.currentThread().getName() + "号车离场了!停了 " + t + " 秒钟");
                    carsNum(-1);//车辆数减1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {                    
                    semaphore.release();//释放车位
                }
            }, String.valueOf(i)).start();
        }
    }

    //统计当前停车数量和剩余车位
    public static void carsNum(int n) {
        cars = cars + n;//当前车辆数
        int pN = MAX_PARKING - cars;//剩余车位
        System.out.println("\n【当前有 " + cars + " 辆车!剩余车位 " + pN + "个】\n");
    }
}

9、读写锁

9.1、简介

ReadWriteLock

包括 读锁 和 写锁

  • 读锁
    • 也叫 共享锁;一次可以有多个线程占有
  • 写锁
    • 也叫 独占锁;一次只能由一个线程占有

9.2、用法

类似于 Lock 锁

private ReadWriteLock lock = new ReentrantReadWriteLock();//定义读写锁

lock.readLock().lock();//添加读锁
try {
    //业务代码
} finally {
    lock.readLock().unlock();//释放读锁
}

lock.writeLock().lock();//添加写锁
try {
    //业务代码
} finally {
    lock.writeLock().unlock();//释放写锁
}

9.3、示例

package readWriteLock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author ajun
 * Date 2021/7/5
 * @version 1.0
 * 读写锁
 */
public class Demo01 {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();//缓存类
        //循环写
        for (int i = 1; i <= 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.push("k-"+temp,"v-"+temp);
            },String.valueOf(i)).start();
        }
        //循环读
        for (int i = 1; i <= 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get("k-"+temp);
            },String.valueOf(i)).start();
        }
    }
}

//自定义加锁缓存类
class MyCacheLock {
    private  volatile Map<String, String> map = new HashMap<>();
    private ReadWriteLock lock = new ReentrantReadWriteLock();//读写锁

    //读
    public String get(String key) {
        lock.readLock().lock();
        String s="";
        try {
            System.out.println(Thread.currentThread().getName() + " 读取 " + key);
            s = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 读取成功");
        } finally {
            lock.readLock().unlock();
        }
        return s;
    }

    //写
    public void push(String key,String value){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 写入 " + key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + " 写入成功");
        } finally {
            lock.writeLock().unlock();
        }
    }
}

10、阻塞队列

10.1、简介

写入:如果队列满了,就阻塞,等待消费

读取:如果队列空了,就阻塞,等待生产

10.2、队列关系图

10.3、使用场景

  • 多线程并发处理

  • 线程池

10.4、四组API

ArrayBlockingQueue

LinkedBlockingQueue

方式抛出异常不会抛出异常阻塞等待超时不等待
添加操作add()offer() 供应put() 无返回值offer(obj,int,timeunit.status)
移除/获取remove() 移除poll() 获得take()poll(int,timeunit.status)
判断队列首部element()peek() 偷看,偷窥
  • remove() :移除,返回元素 public E remove() {}

  • remove(Object o) :移除,返回boolean public boolean remove(Object o) {}

  • offer(obj,int,timeunit.status) :添加、供应

    • obj :需要添加的元素
    • int :等待的时间。如果超出时间添加不成功就退出
    • timeunit.status : 时间单位。如秒
  • poll(int,timeunit.status) :获取

    • int :等待的时间。如果超出时间获取不成功就退出
    • timeunit.status : 时间单位。如秒

1、第一组

add()、remove()、element()
操作不成功时会抛出异常

package blockingQueue;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author ajun
 * Date 2021/7/6
 * @version 1.0
 * 阻塞队列四组API
 */
public class demo01 {
    public static void main(String[] args) {
        api1();
    }

    /*
    add()、remove()、element()
    操作不成功时会抛出异常
     */
    public static void api1() {
        //队列大小
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        //添加
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
        
        //超出队列大小,第四个元素添加不成功,抛出异常
        //java.lang.IllegalStateException
        //System.out.println(queue.add("d"));
        
        System.out.println("-------------");
        System.out.println("队首" + queue.element());
        System.out.println(queue.remove());
        System.out.println("队首" + queue.element());
        System.out.println(queue.remove());
        System.out.println("队首" + queue.element());
        System.out.println(queue.remove());
        
        //超出队列大小,第四个元素清除不成功,抛出异常
        //java.util.NoSuchElementException
        //System.out.println(queue.remove());
    }
}

2、第二组

offer()、poll()、peek()
操作不成功时不抛出异常,有返回值

/*
    offer()、poll()、peek()
    操作不成功时不抛出异常,有返回值
     */
public static void api2() {
    //队列大小
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    //添加
    System.out.println(queue.offer("a"));
    System.out.println(queue.offer("b"));
    System.out.println(queue.offer("c"));

    //超出队列大小,添加不成功,返回false,不抛异常
    System.out.println(queue.offer("d"));

    System.out.println("-------------");
    System.out.println("队首" + queue.peek());
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println("队首" + queue.peek());
    System.out.println(queue.poll());

    //超出队列大小,获取不成功,返回null,不抛异常
    System.out.println("队首" + queue.peek());
    System.out.println(queue.poll());
}

3、第三组

put()、take()
操作不成功时阻塞等待

/*
    put()、take()
    操作不成功时阻塞等待
     */
public static void api3() throws InterruptedException {
    //队列大小
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    //添加
    queue.put("a");//无返回值,不能打印输出
    queue.put("b");
    queue.put("c");
    //queue.put("d"); //队列没有位置就会阻塞

    System.out.println(queue.take());
    System.out.println("队首" + queue.peek());
    System.out.println(queue.take());
    System.out.println("队首" + queue.element());
    System.out.println(queue.take());
    //System.out.println(queue.take());//超出队列中现在元素数量,阻塞等待
}

4、第四组

offer(obj,int,timeunit.status)、poll(int,timeunit.status)
操作不成功时,阻塞等待,等待超时就退出;操作成功时,不需等待,直接退出;

/*
    offer(obj,int,timeunit.status)、poll(int,timeunit.status)
    操作不成功时超时等待
     */
public static void api4() throws InterruptedException {
    //队列大小
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
    //添加
    System.out.println(queue.offer("a", 2, TimeUnit.SECONDS));
    System.out.println(queue.offer("b", 2, TimeUnit.SECONDS));
    System.out.println(queue.offer("c", 2, TimeUnit.SECONDS));
    //超出队列大小,添加不成功。等待2秒后退出,返回false,不阻塞
    System.out.println(queue.offer("d", 2, TimeUnit.SECONDS));

    System.out.println("---------------");

    System.out.println(queue.poll(1, TimeUnit.SECONDS));
    System.out.println(queue.poll(1, TimeUnit.SECONDS));
    System.out.println("队首" + queue.peek());
    System.out.println(queue.poll(1, TimeUnit.SECONDS));
    //获取数量超出队列中现有数量,获取不成功。等待1秒后退出,返回null,不阻塞
    System.out.println(queue.poll(1, TimeUnit.SECONDS));
}

10.5、同步队列

SynchronousQueue

最多只存一个元素

进去一个元素,必须等待取出来之后,才能再往里面放一个元素

存:put()、取:take();用其它存取方法,达不到同步存取目的

package blockingQueue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author ajun
 * Date 2021/7/6
 * @version 1.0
 * 同步队列
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        SynchronousQueue<String> queus = new SynchronousQueue<>();//同步队列
        //存入
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " 存入:1");
                queus.put("1");
                System.out.println(Thread.currentThread().getName() + " 存入:2");
                queus.put("2");
                System.out.println(Thread.currentThread().getName() + " 存入:3");
                queus.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //queus.offer();
            //queus.add();
            //queus.offer(,2, TimeUnit.SECONDS);
        },"存入线程").start();

        //取出
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " 取出:" + queus.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " 取出:" + queus.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " 取出:" + queus.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //queus.remove();
            //queus.poll();
            //queus.poll(2,TimeUnit.SECONDS);
        },"取出线程").start();
    }
}

11、线程池(重点)

三大方法、七大参数、4种拒绝策略

一、池化技术

  • 背景:程序运行: 占用系统的资源 ! 优化CPU资源的使用 ===>池化技术

    • 线程池,连接池,内存池,对象池 …
  • 原理:提前准备好一些资源,如果要用,就来拿;用完之后返还

  • 好处

    • 降低资源消耗
    • 提高响应速度
    • 方便管理

线程复用,可以控制最大并发数,管理线程

二、三大方法

Executors 工具类创建线程池有三种方法

阿里巴巴开发手册禁止用 Executors 创建线程池,而用 ThreadPoolExecutor 的方式

1、单一线程

Executors.newSingleThreadExecutor();//单个线程

2、固定数量线程

Executors.newFixedThreadPool(5);//固定数量线程

3、弹性数量线程

Executors.newCachedThreadPool();//弹性数量线程

package threadPool;

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

/**
 * @author ajun
 * Date 2021/7/6
 * @version 1.0
 * 用 Executors 工具类的三种方法创建线程
 */
public class ExecutorsDemo {
    public static void main(String[] args) {
        //ExecutorService service = Executors.newSingleThreadExecutor();//单个线程
        ExecutorService service = Executors.newFixedThreadPool(5);//固定数量线程
        //ExecutorService service = Executors.newCachedThreadPool();//弹性数量线程
        try {
            for (int i = 1; i <= 10; i++) {
                service.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " ok!");
                });
            }
        } finally {
            service.shutdown();//关闭线程池
        }
    }
}

三、七大参数

ThreadPoolExecutor

1、三大方法底层分析

用 Executors 创建线程池的三种方法,底层都是调用 ThreadPoolExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

2、七大参数

ThreadPoolExecutor 构造器

public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
                          int maximumPoolSize,	//最大的线程池大小
                          long keepAliveTime,	//存活时间
                          TimeUnit unit,	//时间单位
                          BlockingQueue<Runnable> workQueue,  //阻塞队列
                          ThreadFactory threadFactory,//线程工厂,创建线程的,一般不动
                          RejectedExecutionHandler handler) {//拒绝策略
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

3、示例

银行大厅接客流程

核心窗口数量:corePoolSize

最大窗口数量:maximumPoolSize (核心窗口数量 + 备用窗口数量)

窗口空闲时间:keepAliveTime、TimeUnit unit ;达到空闲时间时,窗口关闭

候客区容量:BlockingQueue workQueue 的容量

大厅拒绝接客:当所有窗口都在接客,并且候客区也已满员,大厅不再接客

最大接待数量:最大窗口数量 + 候客区容量

  • 核心窗口接客、队列未满
  • 核心窗口接客、队列已满、陆续开启备用窗口
  • 所有窗口接客、队列未满、大厅仍可以接客
  • 所有窗口接客、队列已满、大厅拒绝接客
  • 所有窗口接客、队列未满、出现空闲窗口,窗口空闲到一定时间开始关闭

四、四种拒绝策略

/**
 * new ThreadPoolExecutor.AbortPolicy() 超出最大处理线程抛出异常
 * new ThreadPoolExecutor.CallerRunsPolicy() 从哪个线程创建就由那个线程执行
 * new ThreadPoolExecutor.DiscardPolicy() 队列满了不会抛出异常
 * new ThreadPoolExecutor.DiscardOldestPolicy() 尝试去和第一个竞争,也不会抛出异常
 */

policy [ˈpɑːləsi]
n. 政策、策略

五、示例

package threadPool;

import java.util.concurrent.*;

/**
 * @author ajun
 * Date 2021/7/6
 * @version 1.0
 */
public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        //线程池
        ExecutorService service = new ThreadPoolExecutor(
                3,//核心线程数
                Runtime.getRuntime().availableProcessors(),//最大线程数 等于 CPU核数
                2,//线程空闲时间
                TimeUnit.SECONDS,//时间单位
                new LinkedBlockingQueue<>(3),//阻塞队列
                Executors.defaultThreadFactory(),//线程工厂
                //new ThreadPoolExecutor.AbortPolicy()//拒绝策略
                //new ThreadPoolExecutor.CallerRunsPolicy()
                //new ThreadPoolExecutor.DiscardPolicy()
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        //调用线程
        try {
            for (int i = 1; i <= 30; i++) {
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                final int temp = i;
                service.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " : " + temp + " -> ok");
                });
            }
        } finally {
            service.shutdown();
        }
    }
}

六、小结

线程池的最大线程数量到底应该如何定义?

  • CPU密集型

    • CPU几核最大线程数就是几;可以保证CPU的效率最高
    • Runtime.getRuntime().availableProcessors() //CPU核数
  • IO 密集型

    • 判断程序中十分耗I/O的线程,大于等于两倍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土味儿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值