juc--并发编程的核心问题总结②

接上篇 juc–并发编程的核心问题总结①

一、读写锁ReadWriteLock

ReadWriteLock维护了一对关联的locks,一个用于只读操作,一个用于写入。
读的时候可以有多个阅读线程同时进行,写的时候只能有一个线程去写。

写锁:独占锁
读锁:共享锁
写线程不能与任何线程共存,只有读线程和读线程可以共存

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();

public class demo9 {
    
    
    public static void main(String[] args) {
    
    
        MyCache myCache = new MyCache();
        //写入
        for (int i = 1; i < 6; i++) {
    
    
            final int temp = i;
            new Thread(() -> {
    
    
                myCache.put(temp+"",20);
            }).start();
        }
        //读
        for (int i = 1; i < 4; i++) {
    
    
            final int temp = i;
            new Thread(() -> {
    
    
                myCache.get(temp+"");
            }).start();
        }
    }
}

/**
 * 自定义缓存
 */
class  MyCache{
    
    
    private volatile Map<String,Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    //写 一个线程写
    public void put(String key,Object value){
    
    
        readWriteLock.writeLock().lock();

        try {
    
    
            System.out.println(Thread.currentThread().getName() + "正在写");
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "写完了");
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            readWriteLock.writeLock().unlock();
        }

    }

    //读 多个线程读
    public void get(String key){
    
    
        readWriteLock.readLock().lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName() + "正在读");
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读完了");
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            readWriteLock.readLock().unlock();
        }

    }
}

在这里插入图片描述

二、阻塞队列BlockingQueue

写入时如果队列满了就必须要阻塞等待,读取时如果队列是空的必须要阻塞等待。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

BlockingQueue有4种不同的API。抛出异常;有返回值,不抛出异常;阻塞一直等待;阻塞等待,超时退出。

1. 抛出异常

public class demo10 {
    
    
    public static void main(String[] args) {
    
    
        test1();
    }

    public static void test1(){
    
    
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3

        System.out.println(blockingQueue.add("a"));//true
        System.out.println(blockingQueue.add("b"));//true
        System.out.println(blockingQueue.add("c"));//true

		System.out.println(blockingQueue.element());//查看队首元素 a
        //Exception in thread "main" java.lang.IllegalStateException: Queue full  队列已满
        //System.out.println(blockingQueue.add("d"));

        System.out.println(blockingQueue.remove());//a
        System.out.println(blockingQueue.remove());//b
        System.out.println(blockingQueue.remove());//c

        //Exception in thread "main" java.util.NoSuchElementException 队列为空
        //System.out.println(blockingQueue.remove());
    }
}

2. 有返回值,不抛出异常

public static void test2(){
    
    
     ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3

     System.out.println(blockingQueue.offer("a"));//true
     System.out.println(blockingQueue.offer("b"));//true
     System.out.println(blockingQueue.offer("c"));//true
     System.out.println(blockingQueue.offer("d"));//false 不抛出异常

	 System.out.println(blockingQueue.peek());//查看队首元素 a

     System.out.println(blockingQueue.poll());//a
     System.out.println(blockingQueue.poll());//b
     System.out.println(blockingQueue.poll());//c
     System.out.println(blockingQueue.poll());//null 不抛出异常
 }

3. 阻塞一直等待

public static void test3() throws InterruptedException {
    
    
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3

    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c");
    //队列已满,阻塞,一直等待
    blockingQueue.put("d");

    System.out.println(blockingQueue.take());//a
    System.out.println(blockingQueue.take());//b
    System.out.println(blockingQueue.take());//c
    //队列已空,阻塞,一直等待
    System.out.println(blockingQueue.take());
}

4. 阻塞等待,超时退出

public static void test4() throws InterruptedException {
    
    
     ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//队列大小为3

     blockingQueue.offer("a");
     blockingQueue.offer("b");
     blockingQueue.offer("c");
     //队列已满,阻塞,等待2s后若还是阻塞就退出
     blockingQueue.offer("d",2, TimeUnit.SECONDS);

     System.out.println(blockingQueue.poll());//a
     System.out.println(blockingQueue.poll());//b
     System.out.println(blockingQueue.poll());//c
     //队列已空,阻塞,等待2s后若还是阻塞就退出
     System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
 }

5. (特殊)同步队列SynchronousQueue

SynchronousQueue不存储元素,入队一个元素必须等待它出队才能再入队。(相当于容量为1)

public class demo11 {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(()->{
    
    
            try {
    
    
                System.out.println(Thread.currentThread().getName() + "put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "put 2");
                blockingQueue.put("2");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
    
    
            try {
    
    
                 System.out.println(Thread.currentThread().getName() + "get 1---"+blockingQueue.take());
                 System.out.println(Thread.currentThread().getName() + "get 2---"+blockingQueue.take());            
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"B").start();
    }
}

在这里插入图片描述

三*、线程池

线程池的好处:(实现线程复用,控制最大并发数和管理线程)

  1. 降低资源消耗 :通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度 :当任务到达时,可以不需要等待线程创建就能立即执行。
  3. 提高线程的可管理性。

线程池有3大方法,7大参数,4种拒绝策略

1. 三大方法

Executors.newSingleThreadExecutor();//创建一个单线程,因此所有提交的任务是顺序执行

public class PoolDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建一个单线程
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        try {
    
    
            for (int i = 0; i < 7; i++) {
    
    
                //使用线程池创建线程 {}中Runnable
                threadPool.execute(()->{
    
    
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

在这里插入图片描述
ExecutorService threadPool = Executors.newFixedThreadPool(int nThreads); //创建一个线程池,大小固定为nThreads个线程,如果没有任务执行,那么线程会一直等待

public class PoolDemo {
    
    
    public static void main(String[] args) {
    
    
		//创建一个线程池,大小固定为5个线程
        ExecutorService threadPool = Executors.newFixedThreadPool(5);      
        try {
    
    
            for (int i = 0; i < 7; i++) {
    
    
                //使用线程池创建线程 {}中Runnable
                threadPool.execute(()->{
    
    
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

在这里插入图片描述
ExecutorService threadPool =Executors.newCachedThreadPool();//创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

public class PoolDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建一个可缓存的线程池
        ExecutorService threadPool =Executors.newCachedThreadPool();
        try {
    
    
            for (int i = 0; i < 7; i++) {
    
    
                //使用线程池创建线程 {}中Runnable
                threadPool.execute(()->{
    
    
                    System.out.println(Thread.currentThread().getName());
                });
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

在这里插入图片描述

2. 七大参数

由三大方法的源码可知,本质是ThreadPoolExecutor():
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ThreadPoolExecutor()源码:
在这里插入图片描述阿里巴巴java开发手册:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和singleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2) CachedThreadPool和scheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

参数举例:
有一家商店,该商店有10个座位,和3个候客区。其中有3个座位是一直开放的,另外7个座位有需要才开放。

开始时,只开放了前三个座位,只有两位客人,3号座位空闲,候客区空闲。
在这里插入图片描述
过了一会,商店涌入了一大批客人,于是商店开放了后面4个座位,此时候客区也坐满了。
在这里插入图片描述
过了两分钟,又进来了一位客人,但是已经没有空余座位了,候客区也满了,这位客人就只能选择等待或者离开。
在这里插入图片描述
若一段时间后客人都走了,后面4个座位1个小时都没有人来,那么就不再对外开放这几个座位(超时释放)。

// 自定义一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        3,// 核心线程
        7, // 最大线程
        3, // 等待3s
        TimeUnit.SECONDS,// 单位 s
        new LinkedBlockingQueue<>(3),// 阻塞队列  候客区大小
        Executors.defaultThreadFactory(),// 线程工厂 一般不变
        new ThreadPoolExecutor.AbortPolicy()// 默认拒绝策略 抛出 RejectedExecutionException异常
 );

3. 四种拒绝策略

在这里插入图片描述

// 默认拒绝策略 抛出RejectedExecutionException异常
ThreadPoolExecutor.AbortPolicy();
// 它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务(由调用线程处理该任务)
ThreadPoolExecutor.CallerRunsPolicy());
// 丢弃队列中最老的任务,并尝试提交新的任务,不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy());
// 队列满了就丢弃任务,不会抛出异常
ThreadPoolExecutor.DiscardPolicy();

4. 如何定义线程池的最大线程数

CPU密集型

CPU密集型也是指计算密集型,大部分时间用来做计算逻辑判断等CPU动作的程序称为CPU密集型任务。

该类型的任务需要进行大量的计算,主要消耗CPU资源。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,CPU集型任务同时进行的数量应当等于CPU的核心数。

IO密集型

IO密集型任务指任务需要执行大量的IO操作,涉及到网络、磁盘IO操作,对CPU消耗较少,其消耗的主要资源为IO。IO密集型的线程数为你判断出的IO密集型线程的两倍。

四*、四大函数式接口

基础:
java Lambda表达式
java 函数式接口
java Stream流

详见:java 函数式接口

下篇 juc–并发编程的核心问题总结③

猜你喜欢

转载自blog.csdn.net/myjess/article/details/121332458