java从零开始之JUC

TimeUnit 时间单位枚举类

作用: 提高时间的可读性

TimeUnit.DAYS 日的工具类 
TimeUnit.HOURS 时的工具类 
TimeUnit.MINUTES 分的工具类 
TimeUnit.SECONDS 秒的工具类 
TimeUnit.MILLISECONDS 毫秒的工具类

TimeUnit.SECONDS.sleep(5)        // 线程休眠5秒
TimeUnit.SECONDS.toMillis(1)     // 1秒转换为毫秒数 
TimeUnit.SECONDS.convert(1, TimeUnit.MINUTES)   // 1分钟转换为秒数 

线程安全的集合

Util包下的集合在多线程下可能会报 java.util.ConcurrentModificationException 并发修改异常

//解决方案:
new Vector<>();   //1.Vector默认线程安全,但效率太低
Collections.synchronizedList(new Arraylist<>());  //2.通过Collections工具类创建线程安全的集合
new CopyOnWriteArrayList<>();  //3.使用JUC包,CopyOnWrite(COW)在写入时复制一个集合,写入完成在插入原集合,避免覆盖

CountDownLatch 闭锁(减法计数器)

一般用于某个线程 A 等待若干个其他线程执行完任务之后,它才执行.(关门的等指定的人数走了再锁门)

new CountDownLatch(int count);  //构造一个给定数值的计数器

countDown();  // 计数-1
getCount();   //返回当前计数
await();  //等待计数器归零,才继续执行代码
await(Long timeout,TimeUnit unit);  //等待计数器归零,或等待指定时间后,继续执行代码

CyclicBarrier 加法计数器

一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行(指定数的运动员都就位了再同时起跑),可复用

new CyclicBarrier(int parties);   //需要parties个线程等待
new CyclicBarrier(int parties,Runnable barrierAction);  //parties个线程等待后,执行barrierAction

await();  //线程等待,直到parties指定数目的线程都等待,才往下执行代码
await(10, TimeUnit.SECONDS);  //等待超时后,线程会被释放,但会向其他线程传播出BrokenBarrierException异常。
isBroken()     //检测Barrier是否被破坏
getNumberWaiting()  //返回当前已等待线程的数量
getParties()  //返回指定的parties
reset()    //将Barrier重置,但会导致已经在等待的线程出现BrokenBarrierException异常并且不会再等待.可能导致凑不齐parties需要的线程而所有后来等待的线程全部阻塞

线程接收到 BrokenBarrierException 异常后就不会再等待了

Semaphore信号量

new Semaphore(int permits);   //创建一个拥有指定数量资源的信号量

acquire();   //获取资源,若无可资源则阻塞
release();   //释放资源

Queue 队列

  • Queue 队列
    • Deque 双端队列
    • BolckingQueue 阻塞队列
      • LinkedBlockingQueue
      • ArrayBlockingQueue
      • SynchronousQueue 同步队列,没有内部容量,插入一个必须取出才能再存

BlockingQueue四种API

抛出异常不抛异常,有返回值阻塞等待超时等待
插入add(E)offer(E)put(E)offer(E,Long,TimeUnit)
取出remove()poller()take()poller(Long,TimeUnit)
检测队首元素,并返回element()peek()

线程池

池化技术: 预先创建好资源,减少创建销毁过程对资源的浪费,同时提供响应速度,方便管理.
线程池: 三大方法,七大参数,四种拒绝策略

Executors

线程池的工厂与工具类,用于创建并返回不同类型的线程池,线程工厂

//Executors的三种创建线程池的方法底层都是创建的ThreadPoolExecutor对象
Executors.newFixedThreadPool(10);  //创建固定线程的线程池
Executors.newSingleThreadExecutor(); //创建单个线程
Executors.newCachedThreadPool();   //创建可伸缩的线程池

ExecutorService

真正执行线程的接口。常见子类ThreadPoolExecutor

void execute(Runnable command) //提交任务,没有返回值,执行Runnable
Future<T> submit(Callable<T> task) //提交任务,有返回值,执行Callable 
void shutdown()//关闭连接池

ThreadPoolExecutor

//创建ThreadPoolExecutor对象的七个参数
int corePoolSize      //核心线程数,空闲时仍保留在池中的线程数
int maximumPoolSize   //最大线程数,cpu密集型同核数,IO密集型大于程序中十分消耗IO的线程
long keepAliveTime    //超过核心线程数的线程,等待被调度的最长等待时间,超时则释放
TimeUnit unit        //超时时间的单位
BlockingQueue<Runnable> workQueue  //阻塞队列,用于已提交任务的排队等待(候客区)
ThreadFactory threadFactory  //程序创建新线程时使用的工厂,一般使用默认的 Executors.defaultThreadFactory()
RejectedExecutionHandler handler  //拒绝策略

阻塞队列的功能:

  • 如果少于corePoolSize线程正在运行,添加一个新线程,而不是排队。
  • 如果大于等于corePoolSize线程正在运行,请求会进入阻塞队列排队而不是添加一个新的线程。
  • 如果阻塞队列已满,则会创建一个新线程,当创建一个新线程后, 线程数超过maximumPoolSize则任务将被拒绝。

排队的三种策略:

  • 直接切换(SynchronousQueue)
  • 无界队列(LinkedBlockingQueue)
  • 有边界的队列(ArrayBlockingQueue)

四种拒绝策略:
当已提交任务数超过 最大线程数+阻塞队列容量 时触发

  • new ThreadPoolExecutor.AbortPolicy: 丢弃任务,抛出RejectedExecutionException拒绝执行异常
  • new ThreadPoolExecutor.CallerRunsPolicy: 让提交任务的线程去执行
  • new ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但不会抛出异常
  • new ThreadPoolExecutor.DiscardOldestPolicy: 与最早的线程竞争,如果失败则丢弃任务,不抛异常

ForkJoin 分治

并行执行任务,在大数据量下提高效率.将大任务差分成小任务再将结果合并.

特点:工作窃取,执行快的线程可以帮执行慢的线程处理任务,双端队列

ForkJoinTask

  • RecursiveAction 递归事件,无返回值
  • RecursiveTask 递归任务,有返回值
fork();  //在当前任务的ForkJoinPool中添加子任务,并异步执行 
invokeAll();  //并行执行子任务

join();   //获取执行结果,如果被中断报RuntimeException
get(); //等待计算完成,然后获取其结果,如果被中断报ExecutionException

例子:

public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;  // 1
    private Long end;    // 1990900000
    // 临界值
    private 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 { // forkjoin 递归
            long middle = (start + end) / 2; // 中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork(); // 拆分任务,把任务压入线程队列
            return task1.join() + task2.join();
        }
    }
} 

ForkJoinPool

ForkJoinPool forkJoinPool=new ForkJoinPool();

submit(ForkJoinTask);  //执行任务,有返回值
execute(ForkJoinTask);  //异步执行任务,无返回值

例子:

long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));

Future

Future 接口表示一个未来可能会返回的结果

get() //获取结果(可能会等待)
get(long timeout, TimeUnit unit) //获取结果,但只等待指定的时间;
cancel(boolean mayInterruptIfRunning) //取消当前任务;
isDone() //判断任务是否已完成。返回true任务已完成,可能是正常终止,异常或取消
isCancelled(); //是否取消,返回ture代表任务完成前被取消

FutureTask 可取消的异步计算,可用来Thread适配Callable

异步回调

CompletableFuture 异步回调(相当于Ajax)

xxx() //表示该方法将继续在已有的线程中执行;
xxxAsync() //表示将异步在线程池中执行。

//静态方法,用于创建CompletableFuture
runAsync(Runnable);  //异步执行Runnable,无返回值
supplyAsync(Supplier);  //异步执行supplier,有返回值
anyOf(CompletableFuture..) //用于并行化多个CompletableFuture。任意一个成功
allOf(CompletableFuture..) //用于并行化多个CompletableFuture。都必须成功

//非静态
whenComplete(BiConsumer<T,U>);  //处理成功结果,T代表执行结果,U代表异常信息
thenAccept(Consumer<T>)  //处理成功结果,T代表执行结果
exceptionally(Function<T,R>);  //处理失败结果,T代表异常信息,R代表函数接口返回值
thenApplyAsync(Function<T,R>) //用于串行化另一个CompletableFuture,接收第一个的返回值,执行第二个并设置返回值

例子:

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 没有返回值的 runAsync 异步回调
//        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
//        });
//        System.out.println("1111");
//        completableFuture.get(); // 获取阻塞执行结果


        // 有返回值的 supplyAsync 异步回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
            int i = 10/0;
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t); // 正常的返回结果
            System.out.println("u=>" + u); // 错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233; // 可以获取到错误的返回结果
        }).get());
}

atomic 原子类

使用原子类来保证原子性,比加锁效率高很多,底层使用Unsafe进行CAS(Compare and swap)操作,比较的是内存中的地址.

  • AtomicInteger 原子的操作Integer
增加值并返回新值:int addAndGet(int delta)1后返回新值:int incrementAndGet()
获取当前值:int get()
用CAS方式设置:int compareAndSet(int expect, int update) //如果当前值为expect则设置为update
  • AtomicReference 原子引用,原子的操作引用数据类型
  • AtomicStampedReference 带版本号的原子引用,解决CAS的ABA问题
//特别注意:这里如果V为包装类,当值不在-128~127之间时会开辟新空间,导致内存中的值与预期的值不在一个地址比较永远为false
AtomicStampedReference<V> atomicStampedReference=new AtomicStampedReference(V initialRef,int initialStamp); //指定初始值和初始版本号

getStamp(); //获取当前版本号
compareAndSet(V expectedReference, V newReference, int expectedStamp,int newStamp); //预期的值和版本号都符合,再更新

锁的理解

CAS 乐观锁

CAS是compare and swap,翻译过来就是比较并交换。乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。在java中,除了Atomic系列类,以及Lock系列类的底层实现,甚至在java1.6以上版本,synchronized在变为重量级锁之前,也会采用CAS机制。

需要维护三个变量值,一个是内存值V,一个是期望的旧的值A,一个是要更新的值B,更新一个变量的时候,只有当预期值A与内存V中的值相等(比较内存地址)的时候,才会执行更新操作,把内存V的值改为B。如果不符合预期会一直循环自旋直到符合.

java无法操作内存,但可以使用native本地方法操作c++,c++可以操作内存.Unsafe类中的方法都是native本地方法

CAS的缺点

1.不符合预期时循环自旋会一直消耗CPU
2.一次性只能保证一个共享变量的原子性
3.ABA问题(一个值从A变成B,又更新回A,普通CAS会误判通过检测。利用版本号机制可以解决)

可重入锁

当线程请求由自己持有的锁时,会成功获取锁.即在一个类的同步方法中调用同一个类的另一个同步方法时会直接获得锁而不会阻塞(前提得是同一个对象或者class),所以java中一个线程可以多次获得一个锁.

如果是非重入锁,在一个同步方法A中调用同步方法B,B会要求A先释放锁才能执行,而A在等待B给它锁才能继续执行,造成了死锁.

自旋锁 Spinlock

自旋锁使用忙等待(反复检查锁是否可用)避免了进程上下文调度的开销,因此对于线程只会阻塞很短时间的场合是有效的。

自定义自旋锁,底层由CAS实现,如果这个锁已经被别人获得,代码进入一个紧凑的循环中反复检查这个锁,直到它变为可用。这个循环就是自旋锁的"自旋"部分。

public class SpinLock {
    private AtomicReference<Thread> cas = new AtomicReference<Thread>();
    public void lock() {
        Thread current = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> mylock");
        //当第一个线程进入后,只有其执行unlock(),其他线程才能结束while循环
        while (!cas.compareAndSet(null, current)) {
        //第一个线程cas执行成功返回true,!取反不循环. 其他线程执行失败,false取反一直循环
            // 循环里为空即可
        }
    }
    public void unlock() {
        Thread current = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> myUnlock");
        cas.compareAndSet(current, null);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值