JUC并发编程(中)

1、读写锁

  • 读锁(共享锁): 这个锁可以被多个线程持有!
  • 写锁(独占锁):这个锁一次只能被一个线程占用!
  • 读写分离,提高效率,在实际业务中去判断哪些代码是只读业务的,不要去锁这些业务
package com.coding.rwlDemo;

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

public class ReadWriteLockDemo {

    public static void main(String[] args) {
        MyCache cache = new MyCache();

        //线程是CPU调度的(每个完整线程执行的顺序是CPU控制的)

        //5个线程写入   会存在线程插队问题
        for (int i = 1; i <= 5; i++) {
            final String tmp=i+"";
            new Thread(()->{
                cache.put(tmp, UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }

        //5个线程读取
        for (int i = 1; i <= 5; i++) {
            final String tmp=i+"";
            new Thread(()->{
                cache.get(tmp);
            },String.valueOf(i)).start();
        }
    }

}

//线程操作资源类  存在插队问题:在没有加读写锁之前,第一个线程还没写入完成,可能会存在其他写入
class MyCache{

    //private Map<String,String> map = new ConcurrentHashMap<>();//为了处理安全
    private Map<String,String> map = new HashMap<>();//两种情况都会存在线程插队问题

    //写 独占锁(按顺序,使用Synchroninzed修饰方法也可以,只是它少了读写这种分开的概念实现)
    public void put(String key,String value){

        System.out.println(Thread.currentThread().getName()+"写入了"+key);
        map.put(key,value);
        //存在别的线程插队
        System.out.println(Thread.currentThread().getName()+"写入完成");
    }

    //读  共享锁
    public void get(String key){

        System.out.println(Thread.currentThread().getName()+"读取了"+key);
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取完成");
    }
}

/**
 * 解决:修改只能一个人修改,读可以大家一起读
 *
 * ReadWriteLock-->ReentrantReadWriteLock  Lock不能区分读和写
 * ReentrantReadWriteLock  可以区分读和写,实现更加精确的控制
 *
 * 多线程下尽量加锁!
 */
class MyCache2{

    //private Map<String,String> map = new ConcurrentHashMap<>();//为了处理安全
    private Map<String,String> map = new HashMap<>();//两种情况都会存在线程插队问题

    //为了解决线程插队问题  就是我还没执行完别的线程就进来执行了
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //写 独占锁(按顺序,使用Synchroninzed修饰方法也可以,只是它少了读写这种分开的概念实现)
    public void put(String key,String value){

        readWriteLock.writeLock().lock();//类似 Lock.lock()

        try {
            System.out.println(Thread.currentThread().getName()+"写入了"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入完成");
        } finally {
            readWriteLock.writeLock().unlock();//类似Lock.unlock()
        }
    }

    //读  共享锁
    public void get(String key){

        readWriteLock.readLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+"读取了"+key);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取完成");
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

2、阻塞队列

队列:不得不阻塞 排队 FIFO 就包含:存 、取

  1. 队列已满必须阻塞
  2. 队列为空必须阻塞
    在这里插入图片描述

为什么使用阻塞队列?
线程之间的通信,如果不关心唤醒,就是用阻塞队列

4组API

方法第一组会抛出异常返回一个布尔值,不会抛出异常延时等待一直等待
插入add()offer(e)offer(e,time)put()
取出remove()poll()poll(time)take()
检查element()peek()--
  1. 第一种:抛出异常
package com.coding.blockingqueqe;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class test1 {

    public static void main(String[] args) {

        //3是队列大小
        //阻塞队列
        BlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

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

        //超出队列大小  会抛出异常提示队列已满IllegalStateException: Queue full
        //System.out.println(blockingQueue.add("d"));

        System.out.println(blockingQueue.element());//检查队首元素是谁
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.element());//检查队首元素是谁
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.element());//检查队首元素是谁
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());//抛出NoSuchElementException
    }
}
  1. 第二种:返回一个布尔值,不会抛出异常
package com.coding.blockingqueqe;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class test2 {

    public static void main(String[] args) {

        //3是队列大小
        //阻塞队列
        BlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //超出队列  不抛出异常  返回 false
        //不希望抛出异常的情况下使用
        System.out.println(blockingQueue.offer("d"));

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

        System.out.println(blockingQueue.poll());//获取队首
        System.out.println(blockingQueue.poll()); 
        System.out.println(blockingQueue.poll()); 

        System.out.println(blockingQueue.poll());//返回 null

        System.out.println(blockingQueue.peek());//查看队首  null
    }
}

3.第三种:一直阻塞

package com.coding.blockingqueqe;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class test3 {

    public static void main(String[] args) throws InterruptedException {

        //3是队列大小
        //阻塞队列
        BlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //blockingQueue.put("d");//超过队列大小一直阻塞

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());//阻塞等待拿出元素

    }
}
  1. 第四种:超时退出
package com.coding.blockingqueqe;

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

public class test4 {

    public static void main(String[] args) throws InterruptedException {

        //3是队列大小
        //阻塞队列
        BlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);


        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //设置超过3秒不等待
        System.out.println(blockingQueue.offer("d",3L, TimeUnit.SECONDS));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS));//null

    }
}

SynchronousQueue队列

  1. 不存储元素,队列是空的
  2. 每一个put操作,必须等待一个take,否则无法继续添加元素
package com.coding.blockingqueqe;

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

public class test5 {
    public static void main(String[] args) {

        //同步队列
        //每一个put操作,必须等待一个take,否则无法继续添加元素
        //不用写队列大小参数
        BlockingQueue<String> queue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+" put a");
                queue.put("a");
                System.out.println(Thread.currentThread().getName()+" put b");
                queue.put("b");
                System.out.println(Thread.currentThread().getName()+" put c");
                queue.put("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+queue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+queue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+queue.take());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
    }
}

3、线程池

  • 三大方法、7大参数、拒绝策略、优化配置

池化技术

  • 程序运行的本质:占用系统资源!提高程序的使用率,降低我们一个性能消耗(线程的创建和销毁,数据库的连接和断开都十分浪费资源,弹性访问,保证系统运行的效率)
  • 线程池、连接池、内存池、对象池 …

为什么要用线程池:线程复用!!

三大方法

package com.coding.blockingqueqe;

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

public class test6 {
    public static void main(String[] args) {

        // 平时我们创建一些类使用工具类操作 
        // 总数可以管理

        //线程池 Executor原生的3大方法

        //固定大小
        ExecutorService threadPool1 = Executors.newFixedThreadPool(2);
        //可以弹性伸缩的线程池,遇强则强(coding老师很好的比喻)
        ExecutorService threadPool2 = Executors.newCachedThreadPool();
        //只有一个
        ExecutorService threadPool3 = Executors.newSingleThreadExecutor();

        try {
            //10个线程进来
            for (int i = 1; i <= 10; i++) {

                //线程池   执行线程
                threadPool1.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" running");
                });
            }
        } finally {
            //线程池关闭
            threadPool1.shutdown();
        }
    }
}
  • 三个方法的源码
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>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

ThreadPoolExecutor七大参数

//线程池最大大小  可代码级别的设置处理器数量
//Runtime.getRuntime().availableProcessors()
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;
    }

线程池底层业务原理
在这里插入图片描述

四大拒绝策略
在这里插入图片描述

思考:工作中怎么使用线程池?只能根据业务情况去自定义线程池的大小策略,禁止使用Executors!
在这里插入图片描述

最大线程池 该如何设置
CPU密集型: 根据CPU的处理器数量来定!保证最大效率
IO密集型: 50 个线程都是进程操作大io资源, 比较耗时! > 这个常用的 IO 任务数!

4、四个函数式接口

java.util.function

所有的函数式接口都可以用来简化编程模型: 都可以使用lambda表达式简化!

/**
 * 函数式接口是我们现在必须要要掌握且精通的
 * 4个!
 * Java 8
 *
 * Function : 有一个输入参数有一个输出参数
 * Consumer:有一个输入参数,没有输出参数
 * Supplier:没有输入参数,只有输出参数
 * Predicate:有一个输入参数,判断是否正确!
 */

Function

package com.coding.funcation;

import java.util.function.Function;
public class Demo01 {
    public static void main(String[] args) {
        //
//        Function<String,Integer> function = new Function<String,Integer>() {
//            @Override
//            public Integer apply(String str) {
//                return str.length();
//            }
//        };

        // (参数)->{方法体}
        Function<String,Integer> function = (str)->{return str.length();};
        System.out.println(function.apply("cjl"));
    }
}

Predicate

package com.coding.funcation;

import java.util.function.Predicate;
public class Demo02 {
    public static void main(String[] args) {

//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String str) {
//                return str.isEmpty();
//            }
//        };

        Predicate<String> predicate = str->{return str.isEmpty();};
        System.out.println(predicate.test("456"));
    }
}

Supplier、Consumer

package com.coding.funcation;

import java.util.function.Consumer;
import java.util.function.Supplier;
public class Demo03 {
    public static void main(String[] args) {

//        Supplier<String> supplier = new Supplier<String>() {
//            // 语法糖
//            @Override
//            public String get() {
//                return "《hello,spring》";
//            }
//        };

        Supplier<String> supplier = ()->{return "《hello,spring》";};
        Consumer<String> consumer = s->{ System.out.println(s);};

//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };

        consumer.accept(supplier.get());

    }
}

5、Stream流式计算

package com.coding.stream;

import java.util.Arrays;
import java.util.List;

// 数据库、集合 : 存数据的
// Stream:计算和处理数据交给 Stream
public class StreamDemo {

    /**
     * 按条件用户筛选:
     * 1、id 为偶数
     * 2、年龄大于24
     * 3、用户名大写   映射
     * 4、用户名倒排序
     * 5、输出一个用户
     *
     * 请你只用一行代码完成!
     */
    public static void main(String[] args) {
        User u1 = new User(1,"a",23);
        User u2 = new User(2,"b",24);
        User u3 = new User(3,"c",22);
        User u4 = new User(4,"d",28);
        User u5 = new User(6,"e",26);

        // 存储
        List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
        // 计算等操作交给流
        // forEach(消费者类型接口)
        users.stream()
                .filter(u->{return u.getId()%2==0;})
                .filter(u->{return u.getAge()>24;})
                .map(u->{return u.getName().toUpperCase();})
                .sorted((o1,o2)->{return o2.compareTo(o1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

6、分支合并

什么是 forkjoin

  1. 任务拆分
  2. 结果合并
    在这里插入图片描述

前提:forkjoin 一定是用在大数据量的情况下,否则效率反而会降低

  1. 工作原理:工作窃取,底层使用的双端队列
  2. 好处:效率高,坏处:会产生资源争夺(最后一个资源)

工作窃取

A: 任务1–>任务2–>任务3–>任务4 A领先执行完成,帮B执行任务

B: 任务1–> 任务2 任务3 任务4

package com.coding.stream;

import java.util.concurrent.ForkJoinPool;
import java.util.stream.LongStream;

public class Test {


    public static void main(String[] args) {
        //test1(); //15433
        //test2();  //10978
        test3(); //812
    }

    //正常计算
    public static void test1(){

        Long start = System.currentTimeMillis();
        Long sum =0L;
        for (Long i = 0L; i <= 10_0000_0000L; i++) {
            sum+=i;
        }
        Long end = System.currentTimeMillis();
        System.out.println("sum:"+sum+"  time:"+(end-start));
    }

    //ForkJoinPool
    public static void test2(){

        Long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool =new ForkJoinPool();
        ForkjoinDemo forkjoinDemo = new ForkjoinDemo(0L,10_0000_0000L);
        Long sum = forkJoinPool.invoke(forkjoinDemo);

        Long end = System.currentTimeMillis();
        System.out.println("sum:"+sum+"  time:"+(end-start));
    }

    //流计算
    public static void test3(){
        Long start = System.currentTimeMillis();
        Long sum = LongStream.rangeClosed(0L,10_0000_1000L).parallel().reduce(0L,Long::sum);
        Long end = System.currentTimeMillis();
        System.out.println("sum:"+sum+"  time:"+(end-start));
    }
}
package com.coding.stream;

import java.util.concurrent.RecursiveTask;

public class ForkjoinDemo extends RecursiveTask<Long> {

    private Long start;
    private Long end;
    private static final Long temp=10000L;//临界值

    public ForkjoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    //计算
    @Override
    protected Long compute() {
        //如果这个数超过中间值  就分任务执行
        if(end-start<=temp){
            Long sum=0L;
            for (Long i = start; i <= end; i++) {
                sum+=i;
            }
            return sum;
        }else {
            Long middle=(end+start)/2;
            ForkjoinDemo left = new ForkjoinDemo(start,middle);
            left.fork();
            ForkjoinDemo right = new ForkjoinDemo(middle+1,end);
            right.fork();

            //合并计算结果
            return left.join()+right.join();
        }
    }
}

7、异步回调CompletableFuture

package com.coding.stream;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * CompletableFuture 异步回调  对将来的结果进行结果  像ajax
 */
public class CompletableFutureDemo {

    public static void main(String[] args) throws Exception {

        //没有返回结果:runAsync  任务执行完了就完毕了
        //可以用于:新增、插入、修改不需要知道返回结果的情况下使用
        // 没有返回值,好比多线程,功能更强大!
        /*CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " 没有返回值");
        });
        System.out.println(future.get());*/


        //有返回结果:supplyAsync(供给型异步)  成功或者失败
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " 有返回值");
            int i=10/0;
            return "1024";
        });

        future1.whenComplete((t,u)->{//正常编译完成
            System.out.println("t===>"+t);//正常时的返回值
            System.out.println("u===>"+u);//返回错误信息

        }).exceptionally(e->{
            System.out.println("异常信息:"+e.getMessage());
            return "555";
        });

        System.out.println(future1.get());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值