JUC知识点总结

JUC

juc->java.util.concurrent:并发编程中的工具类

  • 三个包:concurrent,atomic,locks
进程和线程
  • 进程:后台运行的一个程序,是操作系统分配资源的基本单位
  • 线程:进程内部使用进程分配到的资源的,实现的某些功能,就是线程。
并发和并行
  • 并发:而并发是指两个或多个事件在同一时间间隔发生。
  • 并行:并行是指两个或者多个事件在同一时刻发生。
多线程编程企业级套路
  • 1.在高内聚和低耦合的要求下,线程(匿名内部类) 操作(堆外暴露的方法) 资源类
  • 2.判断/干活/通知
  • 3.多线程交互,判断用while不用if
  • 4.注意标志位的修改和定位
lock
class X{
private final ReentrantLock lock = new ReentrantLock();
    public void m(){
        lock.lock();
        try{
            //method body
        }finally{
            lock.unlock()
        }
    }
}
  • ReentrantLock:可重入锁
java多线程的多种状态
  • 1.NEW:新建

  • 2.RUNNABLE:就绪准备

  • 3.BLOCKED:阻塞

  • 4.WAITTING:一直等

  • 5.TIMED_WATTING:等指定时间

  • 6.TERMINATED:结束

  • wait和sleep的区别:

    • wait:放开资源
    • sleep:不放开资源
Lambda Express
  • 口诀:拷贝小括号,写死右箭头,落地大括号。
  • 函数注释:@FunctionInterface
  • Java8之后可以在接口中有默认实现,因此需要加权限保护为default。
  • 静态方法实现:可以在接口中实现静态方法
线程间通信
Thread.wait()->Thread.notifyAll()
  • 多线程交互中,必须防止多线程的虚假唤醒,即不需使用if只能使用while,因为用if的时候,可能存在判断if之后,执行业务之前产生一次wait,这样导致再次调度到这个线程的时候,可能执行了另一个操作,影响到本身的数据。
  • 用JUC的包,wait->notifyAll变成了:
Condition condition = lock.newCondition();
condition.await();——>condition.signalAll();
  • synchronized的wait/notify 和Condition的await/signal 的区别,可以通过创造多个condition进行精准打击
 private Condition condition1 = lock.newCondition();
 private Condition condition2 = lock.newCondition();
 private Condition condition3 = lock.newCondition();
8锁现象

有两个方法 email和EMS,synchronized修饰

class Test{

	public synchronized void email(){
		System.out.println("email");
		this.thread.sleep(4000);
	}
	
	public synchronized void ems(){
		System.out.println("ems");
	}
}

public void static main(args[]){
	Test test1 = new Test();
	Test test2 = new Test();
	new Thread(()->{
		test1.email();
		test1.ems();
	}).start();	
}
  • 1.标准访问:顺序执行
  • 2.email方法sleep4秒,先打印email,后打印ems
  • 3.新增一个普通方法hello,先打印hello,后打印email和ems
  • 4.如果有两个资源类,第二个线程调用第二个资源类的EMS,先打印EMS
  • 5.两个静态同步方法,同一个资源类,先打印Email,后打印EMS。
  • 6.两个静态同步方法,两个资源类,先打印Email,在打印EMS
  • 7.一个普通同步方法,一个静态同步方法,先打印EMS,再打印邮件
  • 8.一个普通同步方法,一个静态同步方法,两个资源,先打印EMS,再打印email
8锁解释
  • 1.第一个二现象因为普通synchronized修饰的方法,锁的当前的实例对象,因此多个线程不能同时访问同一个实例对象中的synchronized修饰的方法,因此按照线程的启动顺序执行方法。所以1,2两个执行方法顺序是这样的。
  • 2.第三个现象之所以普通方法会先执行,不受锁的影响,因为没有经过synchronized修饰的方法,不在受到锁影响的资源中,可以执行。
  • 3.两个资源类的情况,因为每个线程锁的各自实例对象的资源,并不会影响到其他线程运行自己实例对象的资源。
  • 4.第五个和第六个静态同步方法:因为锁的是Class这个东西,因此,只要是用.Class实例化的对象的静态锁,本质上是一把锁。
  • 5.第七个和第八个,因为一个是锁的静态方法即锁的是这个类,一个锁的是已经实例化的对象,本质上并不是同一个把锁,所以不持有另一个现成的资源,因此,先执行EMS后执行email(email在sl)
List线程不安全
  • 高并发导致线程不安全的异常:java.util.ConcurrentModificationException
  • 不安全的原因:
    • 进行资源操作的方法没有上锁
  • 不安全怎么办
    • 1.用vector这种线程安全的集合
    • 2.用Collections.synchronizedList(new ArrayList<>())
    • 3.用JUC中的 java.util.Concurrent.CopyOnWriteArrayList.class
  • vector为什么线程安全:因为add方法经过synchronized的修饰
  • Collections.synchronizedList:他的add方法是这样的:
	synchronized (mutex) {list.add(index, element);}}
  • CopyOnWriteArrayList:写时复制
public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
Set线程不安全
  • 高并发导致的问题跟list一样,ConcurrentModificationException
  • 解决办法:
    • CopyOnWriteArraySet类
  • HashSet底层是HashMap 放到HashMap的Key的位置,value是一个
	private static final Object PRESENT = new Object();
  • HashSet的add()底层源码就是实现HashMap的put()
Map线程问题
  • HashMap底层是一个队列+链表+红黑树的数据结构,他的负载因子是0.75初始值大小是16
  • HashMap的线程并不安全,因此可以使用HashTableConcurrentHashMap
    • HashTable在修改数据是锁住了整个HashTable,效率较低
    • ConcurrentHashMap把整个Map分为N个Segment,提供相同的线程安全,运用了锁分离技术。
  • HashMap调优可以选择初始值设置较大,减少扩容次数使用的时间。
  • HashMap中的链表长度大于8,就会把单向链表变化成红黑树jdk1.7之前采用头插法jdk1.8之后采用尾插法,因为采用头插法在扩容的时候可能会产生环形链表。
多线程获取方法
  • 常见的方法
    • 1.Thread
    • 2.实现Runnable接口
    • 3.实现Callable结构
    • 4.线程池(重点)
  • Callable和Runnable接口的区别
    • 1.方法不同,一个是run,一个是call
    • 2.call会抛出异常
    • 3.call有返回值
  • Theard的构造器必须实现了Runnable,因此我们需要找一个实现了Runnable和与Callable有关的类,即FutureTask类。
  • 如果通过两个线程调用同一个FutureTask,返回值会存在缓存中,因此只会执行一次call操作。
JUC的辅助工具类
  • CountDownLatch类:倒计时,await取消的要求就是Count=0,底层有个Sync的类做封装。
  • CyclicBarrier类:等指定的数量暂停,执行构造器里面的Run代码
  • Semaphore类:控制多线程的并发数量
ReenTrantReadWriteLock读写锁
  • 多个线程访问一个资源类,不会产生并发的问题,因此为了满足并发量,读取共享资源应该可以同时进行。
  • 但是写操作会产生资源的变化,因此对写操作要进行锁。
  • 跟Lock的区别,Lock的锁是读写都锁,效率低,readWriteLock读写分离。
  • 获取读锁和写锁的方法是:
	ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	readWriteLock.writeLock().lock();
	readWriteLock.readLock().lock();
BlockingQueue
  • 当队列是空的,从队列中获取元素的操作将会被阻塞。
  • 当队列是满的,向队列中添加元素的操作将会被阻塞。
  • BlockingQueue的好处是我们不需要关心什么时候需要阻塞线程,这一切交割BlockingQueue解决。
  • 常用实现类
    • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
    • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
    • DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
    • SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
    • LinkedTransferQueue:由链表组成的无界阻塞队列。
    • LinkedBlockingDeque:由链表组成的双向阻塞队列。
ThreadPool线程池
  • 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
  • 线程池的优势:
    • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
    • 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
    • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 重要的接口:
    • Executor接口
    • ExecutorService接口
  • 实现线程池的类是:ThreadPoolExcutor
  • 创建线程池的工具类:Excutors,可以创建如下的线程池:
    • newFixedThreadPool(5)
    • newSingelThreadPool()
    • newCacheThreadPool():执行很多短期异步任务,线程池根据需要创建新线程,先前构建的线程可以复用。
  • ThreadPoolExcutor源代码:上面的工具类返回值都是ThreadPoolExcutor类。
  • 线程池几个重要参数:
    • corePoolSize:线程池中常驻核心线程数量
    • maxmumPooSize:线程池中能够容纳同时执行的最大线程数,必须大于等于1。
    • keepAliveTime:多余空闲线程的存活时间,当前线程超过corePoolSize是,当空闲时间达到keepAliveTime是,多于线程池会被销毁
    • unit:keepAliveTime的单位
    • workQueue:任务队列,被提交但是尚未被执行的任务
    • threadFactory:生成线程池中工作线程的线程工厂,用于创建线程,一般默认即可。(可以通过实现ThreadFactory接口自己实现)
    • handler:拒绝策略,当队列满了,并且工作线程大于等于线程池的最大线程数时,如何来拒绝请求执行的runnable的策略。
  • 线程池底层工作原理:
    • 初始开启corePoolSize
    • 当请求线程大于corePolsize,就将后来的请求放入Blocking Queue
    • 当BlockQueue满了,将当前常驻线程数进行扩容
    • 如果当所有的都满了,就开始执行reject策略
  • 工作中只允许用自己创建的线程池,不允许用jdk自带,参考阿里巴巴java开发手册,因为JKD自带的max数量是21亿多,会导致OOM
  • 自定义线程池:手撕线程池!!!
package juc;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                new MyThreadFactory("jams"),
                new ThreadPoolExecutor.AbortPolicy());

        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "执行");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

  • 自定义线程工厂
package juc;

import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class MyThreadFactory implements ThreadFactory {
    private final String prefix;
    private final AtomicInteger nextId = new AtomicInteger(1);

    //通过构造器决定这个工厂生产哪个组的线程
    MyThreadFactory(String groupFeatureName) {
        prefix = "MyThreadFactory " + groupFeatureName + "-Worker-";
    }

    @Override
    public Thread newThread(Runnable r) {
        String name = prefix + nextId.getAndIncrement();
        Thread thread = new Thread(null, r, name, 0);

        return thread;
    }
}

  • 四种拒绝策略:
  • AbortPolicy():拒绝接受的情况下,直接弹出RejectedExecutionException异常
  • CallerRunsPolicy():该策略既不会抛弃任务,也不会抛出异常,而是将某些任务返回给调用者执行。
  • DiscardPolicy():不会报错,但是超过数量的请求,直接抛弃不执行。
  • DiscardOldestPolicy():按照先来后到,丢弃最后的任务
  • 参数选择
    • maximumPoolSize:
      1.cpu密集型:核数+1/2
      2.IO密集型:核数/阻塞系数
JAVA8流式处理
  • 主要接口:
    • java.util.Stream
  • java.util.Function四大函数式接口
    • Consumer:消费性接口 ->有输入T,没输出
    • Supplier:供给型接口 ->没输入,有输出T
    • Function<T,R>:函数型接口 ->有输入T,有输出R
    • Predicate:断定型接口 -> 有输入T,有输出true/false
  • 语法和api跟scala的rdd一样
ForkJoinPool
  • ForkJoinPool是线程池ExecurotService的实现类。
  • 通过继承RecursiveTask抽象类,实现compute()方法
  • 作用和hadoop的MapReduce有点像,分为fork和join两步,对应map和reduce(combine)
    代码
	package juc;
	
	
	import java.util.concurrent.ExecutionException;
	import java.util.concurrent.ForkJoinPool;
	import java.util.concurrent.ForkJoinTask;
	import java.util.concurrent.RecursiveTask;
	
	class MyFork extends RecursiveTask<Integer> {
	    private int start;
	    private int end;
	    private int result;
	
	    public MyFork(int start, int end) {
	        this.start = start;
	        this.end = end;
	    }
	
	    @Override
	    protected Integer compute() {
	        if (end - start < 10) {
	            for (int i = start; i <= end; i++) {
	                result += i;
	            }
	        } else {
	            int mid = (start + end) / 2;
	            MyFork fork1 = new MyFork(start, mid);
	            MyFork fork2 = new MyFork(mid + 1, end);
	            //先fork下去,再join
	            fork1.fork();
	            fork2.fork();
	
	            result = fork1.join() + fork2.join();
	        }
	        return result;
	    }
	
	}
	
	public class ForkJoinDemo02 {
	    public static void main(String[] args) throws ExecutionException, InterruptedException {
	        MyFork fork = new MyFork(0, 100);
	        ForkJoinPool forkJoinPool = new ForkJoinPool();
	        
	        ForkJoinTask<Integer> submit = forkJoinPool.submit(fork);
	        System.out.println(submit.get());
	        
	        forkJoinPool.shutdown();
	    }
	}

异步回调
  • CompletableFuture类
    • 方法:runAsync()有返回值和没有返回值两种,异步执行
    • 方法:supplyAsync()没有输入,有返回值,异步执行
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值