JUC笔记续

读写锁

ReadWriteLock

读可以被多个线程同时读,写只能有一个线程写

独占锁(写锁)

共享锁(读锁)

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

/*
自定义缓存
*/
class MyCache{
  private volatile Map<String, Object> map - new HashMap<>();
  //读写锁,更加细粒度的控制
  private ReadWriteLock RWlock = new ReentrantReadWriteLock();
  
  //写入的时候,只希望同时只有一个线程在写
  public void put(String key, Object value){
    RWlock.writeLock().lock();//写锁
    
    //try
    System.out.println(Thread.currentThread().getName()+"写入"+key);
    map.put(key, value);
    System.out.println(Thread.currentThread().getName()+"写入OK");
    
    //finally
    RWlock.writeLock().unlock();//解锁
  }
  
  //读取,所有人都可以读
  public void get(){
    RWlock.readLock().lock();//读锁
    //try
    System.out.println(Thread.currentThread().getName()+"读取"+key);
    Object o = map.get(key);
    System.out.println(Thread.currentThread().getName()+"读取ok");
    //finally
    RWlock.readLock().unlock();//解锁
  }
}

阻塞队列

BlockingQueue

写入:如果队列满了,就必须阻塞等待

取:如果队列为空,必须阻塞等待生产

什么情况下使用阻塞队列:多线程并发处理,线程池

public class Test{
  public static void main(String[] args){
    
  }
}
学会使用队列

添加、移除、判断队列首部

四组API:

ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

  1. 抛出异常

    容量为3,如果超出容量

    IllegalStateException:Queue full 抛出异常

    如果队列为空,还删除

    java.util.NoSuchElementException 抛出异常

    检查队首元素

    System.out.println(blockingQueue.element());

  2. 不会抛出异常

    添加元素

    System.out.println(blockingQueue.offer("a")); 返回true或者false

    删除元素

    System.out.println(blockingQueue.poll()); 返回元素值或null

    检查队首元素

    System.out.println(blockingQueue.peek());

  3. 阻塞 等待

    添加元素

    blockingQueue.put("a"); 没有返回值

    如果添加多了元素,队列没有位置了,一直等待

    删除元素

    System.out.println(blockingQueue.take()); 打印出相应元素

    如果队列为空,还在删除元素,一直等待

  4. 超时等待(超了时间就不等了)

    添加元素

    blockingQueue.offer("a",2,TimeUnite.SECONDS); 等待两秒,还没有位置就结束程序 ,offer(元素,等待时间,等待单位);

    删除元素

    blockingQueue.poll(2, TimeUnit.SECONDS); 等待两秒,如果队列还为空,结束程序

| ---------------------------------------------------------------- |
| | 方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 | |
| | ---- | -------- | -------- | -------- | ----------- | |
| | 添加 | add | offer() | put() | offer(,,) | |
| | 删除 | remove | poll() | take() | poll(,) | |
| | 队首 | element | peek() | - | - | |
| |

SynchronousQueue同步队列

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

put,take

和其他BlockingQueue不一样,SynchronousQueue不存储元素,put之后必须take才能继续put

public class SynchronousQueueDemo{
  public static void main(String[] args){
    BlockingQueue<String> bQueue = new SynchronousQueue<>();
    
    new Thread(()->{
      System.out.println(Thread.currentThread().getName()+"put 1");
      System.out.println(Thread.currentThread().getName()+"put 2");
      bQueue.put("2");
      System.out.println(Thread.currentThread().getName()+"put 3");
      bQueue.put("3");
    },"T1").start();
    
    new Thread(()->{
      TimeUnit.SECONDS.sleep(3);
      System.out.println(Thread.currentThread().getName()+bQueue.take());
      TimeUnit.SECONDS.sleep(3);
      System.out.println(Thread.currentThread().getName()+bQueue.take());
      TimeUnit.SECONDS.sleep(3);
      System.out.println(Thread.currentThread().getName()+bQueue.take());
    },"T2").start();
  }
}

线程池:3方法、7参数、4拒绝策略(重要

池化技术

程序的运行,本质:占用系统的资源,优化资源的使用 =>池化技术

线程池、连接池、内存池、对象池... //创建,销毁十分浪费资源

池化技术:事先准备好一些资源,有人要用就来拿,用完之后还回来。

线程池的好处:

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

  1. 降低资源的消耗
  2. 提高响应的速度
  3. 方便管理
三大方法
public class Demo1{
  //Executors 工具类、3大方法
  public static void main(String[] args){
//    Executors.newSingleThreadExecutor();//单个线程
//    Executors.newFixedThreadPool(5);//固定的线程池大小:5个线程
//    Executors.newCachedThreadPool();//可伸缩的
//可伸缩的线程池范围是0-21亿,会造成OOM,这就是为什么不让使用Executor创建线程池,要通过ThreadPoolExecutor的方式
    
    ExecutorService threadPoll = Executors.newSingleThreadExecutor();
    //try
    for(int i=0; i<10; i++){
      //使用了线程池之后,使用线程池创建线程
      threadPoll.execute(()->{
        System.out.println(Thread.currentThread.getName());
      });
    }
    //线程池用完,程序结束,关闭线程池
    //finally
    threadPool.shutdown();
  }
}
七大参数

线程池本质:调用ThreadPoolExecutor()

public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                         int maximumPoolSize,//最大核心线程池大小
                         long keepAliveTime,//超时了没有人调用就会释放
                         TimeUnit unit,//超时单位
                         BlockingQueue<Runnable> workQueue,//阻塞队列
                         ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
                         RejectedExectionHandler handler)//拒绝策略

例子:关于银行
刚开始就开的柜台是核心线程池,候客区满了会多开几个柜台,就是最大线程池,等待的地方就是阻塞队列,当阻塞队列也满了,就是拒绝策略

根据上面的例子,创建线程池

ExecutorService threadPool = new ThreadPoolExecutor(2,
                                                    5,
                                                    3,//后开的线程超过3秒没人调用就释放了
                                                    TimeUnit.SECONDS,
                                                   new LinkedBlockingDeque<>(3),//候客区的容量是3
                                                   Executors.defaultThreadFactory(),//默认的线程工厂
                                                   new ThreadPoolExecutor.AbortPolicy()//拒绝策略
                                                   );
//最大承载是:Deque的容量+max线程池大小
//超过这个数量 就会触发拒绝策略 抛出异常 RejectedExecutionException
四种拒绝策略
  1. new ThreadPoolExecutor.AbortPolicy() 满了还有人进来,不处理这个人,抛出异常
  2. new ThreadPoolExecutor.CallerRunsPolicy() 哪来的去哪里 例如:main
  3. new ThreadPoolExecutor.DiscardPolicy() 队列满了,丢掉任务,不抛出异常
  4. new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了,尝试去和最早的竞争,也不会抛出异常
最大线程池的大小到底该如何定义?

(用于调优)

  1. CPU密集型 几核,就定义为几,可以保证CPU的效率最高

    Runtime.getRuntime().avaliableProcessors(); 获取CPU的核数

  2. IO密集型

    判断程序中十分耗IO的线程有多少个,大于这个数就可以了(因为IO十分占用资源)

四大函数式接口(重要重要)

函数式接口:只有一个方法的接口

eg:

@FunctionalInterface
public interface Runnable{
  public sbatract void run();
}
//简化编程模型,在框架底层大量应用
//foreach(消费者类型的函数接口)

四大函数式接口

Consumer、Function、predicate、Supplier

/*
Function 函数型接口,有一个输入参数,有一个输出
只要是函数式接口,都可以用Lambda
*/
public class Demo1{
  publilc static void main(String[] args){
    //工具类:输出输入的值
    Function function = new Function<String, String>(){
      //泛型:传入一个String,返回一个String
      public String apply(String o){
        return o;
      }
    };
    //Lambda
    Function<String,String> function = (str)->{return str;};
    //调用
    function.apply("FUNCTION");
  }
}
/*
Predicate 断定型接口,返回值只能是布尔值
*/
public class Demo2{
  public static void main(String[] args){
    Predicate<String> predicate = new Predicate<String>(){
      //输入一个String,输出是boolean值
      public boolean test(String o){
        return false;
      }
    };
    Predicate<String> predicate = (str)->{return str.isEmpty();};
  }
}
/*
Consumer 消费型接口
*/
public class Demo3{
  public static void main(String[] args){
    Consumer<String> consumer = new Consumer<String>(){
      //只有输入,没有返回值
      puclic void accept(String o){
        System.out.println(o);
      }
    };
   	Consumer<String> consumer = (str)->{System.out.println(o);};
  }
}
/*
Supplier 供给型接口
*/
public class Demo4{
  public static void main(String[] args){
    Supplier supplier = new Supplier<Integer>(){
      //没有参数,只有返回值
      public Integer get(){
        return 1024;
      }
    };
    
    Supplier supplier = ()->{return 1024;};
  }
}

四个函数式接口的作用

代码更简洁

Stream流式计算

大数据:存储+计算

存储:集合、MySQL

计算交给流完成

/*
要求:
筛选用户
1. ID是偶数
2. 年龄大于23
3. 用户名转化为大写字母
4. 用户名字母为倒序
5. 只输出一个用户
*/
public class Test{
  public static void main(String[] args){
    User u1 = new User(1,"a",21);
    User u2 = new User(2,"b",22);
    User u3 = new User(3,"c",23);
    User u4 = new User(4,"d",24);
    User u5 = new User(6,"e",25);
    //集合就是存储
    List<User> list = Arrays.aslist(u1,u2,u3,u4,u5);
    
    //计算交给Stream
    //链式编程
    list.stream()
      .filter(u->{return u.getId()%2 == 0;})
      .filter(u->{return u.getAge()>23;})
      .map(u->{return u.getName().toUpperCase();})
      .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
      .limit(1)
      .forEach(System.out::println);
  }
}

ForkJoin

并行执行任务,在大数据量的情况下提高效率,小数据量没必要

把大任务拆分为小任务

ForkJoin特点:工作窃取

先做完自己的工作的去别人那里拿没做完的工作继续做

ForkJoin的操作

类似递归

.fork(); 拆分任务,把任务压入线程队列

.join()+.join(); 把结果加到一起

forkJoinPool.submit(task); 提交任务

forkJoinPool.execute(task); 执行任务

submit.get(); 获取结果

异步回调

Future设计的初衷:对未来的get方法返回的结果类型,对将来的某个事件的结果进行建模

异步调用:Ajax

实现类:CompletableFuture 异步执行、成功回调、失败回调

没有返回值的

CompletableFuture<Void> comF = CompletableFuture.runAsync();

comF.get(); 获取结果,如果阻塞,就一直等着直到获取结果

有返回值的

CompletableFuture<Integer> comF = CompletableFuture.supplyAsync();

comF.whenComplete((t,u)->{

System.out.println("t"+t+"u"+u);})

.execeptionally((e)->{

System.out.println(e.getMessage());

return 233;

}).get());

执行输出t(正常的返回结果,有错时是null)和u(没错时是null,有错误时是错误信息)两个参数。有错误的话输出获取到的错误信息,然后输出233

posted @ 2021-02-23 10:47  GladysChloe  阅读(49)  评论(0)    收藏  举报