此篇文章是在B站学习尚硅谷2021版JUC时所做的笔记,用于复习,如需深度学习请点击蓝色文字。
文章目录
- JUC学习
 
JUC学习
什么是JUC
- 是并发编程的一些工具。
 - 是java.util.concurrent工具包的简称,是处理线程用的。
 
并发与并行
- 串行是一次只能取一个任务,并执行这个任务。
 - 并行多个CPU同时执行多个任务。比如:多个人同时做不同的事。
 
用户线程:自定义线程。
守护线程:在后台运行的;比如垃圾回收。
线程锁
Synchronized:是自动解锁。
 如果以修饰方法的形式出现,它的同步监视器是this;如果是代码块的方式出现锁的同步监视器需要自己设置。
Lock:需要手动解锁
是需要创建出Lock的对象。
ReentrantLock lock = new Reentrantlock();
Synchronized和Lock的区别:
 Lock不会自动释放锁,在发生异常时,不会主动释放锁,就可能造成死锁,因此可以在finally块中释放锁;Synchronized在发生异常时会自动释放锁,因此不会导致死锁。
线程间的通信
Synchronized锁的通信
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
虚假唤醒:wait()有一个特定,在哪睡着,就在哪醒。
Lock锁的通信
创建锁的通信
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
condition.await();
condition.notify();
condition.notifyAll();
 
 
解决线程不安全问题
1.解决ArrayList线程不安全的方法
方式一:使用Vector(),作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储。
List<String> list = new Vector();
 
方式二:通过Collentions工具类来解决线程不集合线程不安全问题。
List<String> list = Collections.synchronizedList(new ArrayList<>());
 
方式二:通过java.util.concurrent工具包里的CopyOnWriteArrayList类(写时复制技术)决集合线程不安全问题。
List<String> list = new ConpyOnWriteArryList();
 
2.解决Hashset线程不安全问题
通过java.util.concurrent工具包里的CopyOnWriteHashset类(写时复制技术)决集合线程不安全问题。
Set<String> list = new ConpyOnWriteHashset();
 
3.解决HashMap线程不安全问题
通过java.util.concurrent工具包里的ConcurrentHashMap类(写时复制技术)决集合线程不安全问题。
Map<String,Object> map = new ConcurrentHashMap<>();
 
多线程锁
下面是一些synchroized的一些细节问题
synchronized实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法,锁是Synchronized括号里配置的对象。
 
公平锁和非公平锁
Lock lock = new ReentrantLock(false);	//设置非公平锁
Lock lock = new ReentrantLock(true);	//设置公平锁
 
默认是非公平锁
非公平锁:线程饿死,效率高。 公平锁:阳光普照,效率相对低。
死锁
1.什么是死锁

//这是死锁
public class DeadLock {
    public static void main(String[] args) {
        DeadLock a = new DeadLock();
        DeadLock b = new DeadLock();
        new Thread(()->{
            synchronized (a){
                System.out.println("a");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b){
                    System.out.println("b");
                }
            }
        }).start();
        new Thread(()->{
            synchronized (b){
                System.out.println("b");
                synchronized (a){
                    System.out.println("a");
                }
            }
        }).start();
    }
}
 
2.产生死锁的原因
- 系统资源不足
 - 进程运行推进顺序不合适
 - 资源分配不当
 
3.验证是否是死锁
代码运行时出现不会动,也不会停止,就一直耗着;分不清是死循环,还是死锁就可以在idea终端下输入以下内容:
	jps -l
	就会出现 编号 + 全类名,找到和你当前正在运行的代码的全类名对应的编号;在输入 jstack 编号;就可以验证是否是死锁了。
 
JUC强大的辅助类
CountDownLatch:作用减少计数
锁门例子:
package com.jj.juc;
import java.util.concurrent.CountDownLatch;
//演示 CountDownLatch
public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) {
        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);
        //6个同学陆续离开教室之后
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "号同学离开了教室。");
                //计数-1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        try {
            //等待
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("班长锁上教室的门。");
    }
}
 
CyclicBarrier:循环栅栏

收集龙珠例子:
package com.jj.juc;
import java.util.concurrent.CyclicBarrier;
//收集七颗龙珠就可以召唤神龙了
public class CyclicBarrierDemo {
    //创建固定值
    private static int NUMBER = 7;
    public static void main(String[] args) {
        //创建CyclicBarrier对象
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
            System.out.println("******已收集七颗龙珠可以召唤神龙了。");
        });
        for (int i=1;i<=7;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"号龙珠已收集。");
                //等待
                try {
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
 
Semaphore:信号灯

停车位例子:
package com.jj.juc;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
//6辆汽车,3个停车位
public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    //占位
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到了车位。");
                    //设置随机停车时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println(Thread.currentThread().getName()+"-----离开了车位。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}
 
ReentrantReadWriteLock(读写锁)
- 独占锁(写锁):一次只能被一个线程占有。
 - 共享锁(读锁):多个线程可以同时占有。
 
package com.jj.juc;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//用来模拟一个缓存
class MyCache{
    private Map<String,Object> map = new HashMap<>();
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //存,写入的时候,只希望只有一个线程操作
    public void put(String key,Object value){
        try {
            readWriteLock.writeLock().lock();
            System.out.println("写入:" + Thread.currentThread().getName());
            map.put(key, value);
        } finally {
            readWriteLock.writeLock().unlock();
            System.out.println("成功写入:" + Thread.currentThread().getName());
        }
    }
    //读,所有人都可以读取
    public void get(String key){
        try {
            readWriteLock.readLock().lock();
            System.out.println("读取:" + Thread.currentThread().getName());
            map.get(key);
        } finally {
            System.out.println("成功读取:" + Thread.currentThread().getName());
            readWriteLock.readLock().unlock();
        }
    }
}
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //写入
        for (int i = 1;i <= 6;i++){
            int temp = i;
            new Thread(()->{
               myCache.put(Thread.currentThread().getName(),temp);
            }).start();
        }
        //读取
        for (int i = 1;i <= 6;i++){
            new Thread(()->{
                myCache.get(Thread.currentThread().getName());
            }).start();
        }
    }
}
 
添加锁(synchronized和ReentrantLock)和 读写锁的区别
添加锁:读读、读写、写写,都是独占的,每次只能操作一个。
读写锁:读读 可以共享,提升性能,同时多人进行操作。缺点:造成锁饥饿,一直读。没有写操作;读时候,不能写,只有读完成之后,才可以写;写操作可以读。
锁降级
package com.jj.juc;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo1 {
    public static void main(String[] args) {
        //可重入,读写锁对象
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        //创建一个读锁
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        //创建一个写锁
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
        
        //锁降级
        //1.获取写锁
        writeLock.lock();
        System.out.println("------write");
        //2.获取读锁
        readLock.lock();
        System.out.println("-----read");
        //3.释放写锁
        writeLock.unlock();
        //4.释放读锁
        readLock.unlock();
    }
}
 
BlockingQueue(阻塞队列)
阻塞队列的概述

当队列是空的,从队列中获取元素的操作将会被阻塞。
当队列是满的,从队列中添加元素的操作将会被阻塞。
常见的BlockingQueue
ArrayBlockingQueue(常用)
LinkedBlockingQueue(常用)
DelayQueue
PriorityBlockingQueue
SynchronizedQueue
LinkedTransferQueue
LinkedBlockingDeque
BlockingQueue核心方法

BlockingQueue 的核心方法:
1.放入数据
	offer(anObject):表示如果可能的话,将 anObject 加到 BlockingQueue 里,即
如果 BlockingQueue 可以容纳,则返回 true,否则返回 false.(本方法不阻塞当
前执行方法的线程)
	offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定
的时间内,还不能往队列中加入 BlockingQueue,则返回失败
	put(anObject):把 anObject 加到 BlockingQueue 里,如果 BlockQueue 没有
空间,则调用此方法的线程被阻断直到 BlockingQueue 里面有空间再继续.
2.获取数据
	poll(time): 取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等
time 参数规定的时间,取不到时返回 null
	poll(long timeout, TimeUnit unit):从 BlockingQueue 取出一个队首的对象,
如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知
道时间超时还没有数据可取,返回失败。
	take(): 取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断
进入等待状态直到 BlockingQueue 有新的数据被加入;
	drainTo(): 一次性从 BlockingQueue 获取所有可用的数据对象(还可以指定
获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加
锁或释放锁。
 
//入门案例
package com.jj.queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
 * 阻塞队列
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // List list = new ArrayList();
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        //第一组
        // System.out.println(blockingQueue.add("a"));
        // System.out.println(blockingQueue.add("b"));
        // System.out.println(blockingQueue.add("c"));
        // System.out.println(blockingQueue.element());
        //System.out.println(blockingQueue.add("x"));
        // System.out.println(blockingQueue.remove());
        // System.out.println(blockingQueue.remove());
        // System.out.println(blockingQueue.remove());
        // System.out.println(blockingQueue.remove());
        // 第二组
        // System.out.println(blockingQueue.offer("a"));
        // System.out.println(blockingQueue.offer("b"));
        // System.out.println(blockingQueue.offer("c"));
        // System.out.println(blockingQueue.offer("x"));
        // System.out.println(blockingQueue.poll());
        // System.out.println(blockingQueue.poll());
        // System.out.println(blockingQueue.poll());
        // System.out.println(blockingQueue.poll());
        // 第三组
        // blockingQueue.put("a");
        // blockingQueue.put("b");
        // blockingQueue.put("c");
        // //blockingQueue.put("x");
        // System.out.println(blockingQueue.take());
        // System.out.println(blockingQueue.take());
        // System.out.println(blockingQueue.take());
        // System.out.println(blockingQueue.take());
        // 第四组
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.offer("a",3L, TimeUnit.SECONDS));
    }
}
 
小结:
- 在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会被自动被唤起
 
线程池
连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度
特点:
- 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
 - 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
 - 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
 
线程池使用方式
Executors.newFixedThreadPool(int):一池N线程。
Executors.newSingleThreadExecutor():一个任务一个任务执行,一池一线程。
Executors.newCachedThreadPool():线程池根据需求创建线程,可扩容,遇强则强。
代码示例
package com.jj.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //创建一池n线程
        ExecutorService thread1 = Executors.newFixedThreadPool(5);
        //创建一池一线程
        ExecutorService thread2 = Executors.newSingleThreadExecutor();
        //创建一池扩容线程
        ExecutorService thread3 = Executors.newCachedThreadPool();
        //10个顾客
        try {
            for (int i = 1; i <= 20; i++) {
                thread3.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        } finally {
            thread3.shutdown();
        }
    }
}
 
具体代码中的七个参数讲解:
int corePoolSize: 常驻线程数量(核心)
 int maximumPoolSize:最大线程数量
 long keepAliveTime,TimeUnit unit:线程存活时间
 BlockingQueue<Runnable> workQueue:阻塞队列(排队的线程放入)
 ThreadFactory threadFactory:线程工厂,用于创建线程
 RejectedExecutionHandler handler:拒绝策略(线程满了)
线程池底层工作原理和拒绝策略
ExecutorService thread2 = Executors.newSingleThreadExecutor() 在执行创建对象时不会创建线程;只有在执行execute()方法时才会创建线程;要执行的任务先被常驻线程执行,常驻线程满了在到阻塞队列进行等待;这时候又有新任务过来,会扩容新的线程,新线程不会执行阻塞队列上的等待的任务,而是执行新过来的任务。注意:扩容的线程数量+常驻线程数量不能大于最大线程。
当大于最大线程数和阻塞队列之和后,会执行拒绝策略。


自定义线程
package com.jj.pool;
import java.util.concurrent.*;
//自定义线程池创建
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        //10个顾客
        try {
            for (int i = 1; i <= 20; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }
}
 
Fork/Join分支合并框架
Fork/Join 它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子 任务结果合并成最后的计算结果,并进行输出。
class Fibonacci extends RecursiveTask<Integer> {
   final int n;
   Fibonacci(int n) { this.n = n; }
   Integer compute() {
     if (n <= 1)
        return n;
     Fibonacci f1 = new Fibonacci(n - 1);
     f1.fork();
     Fibonacci f2 = new Fibonacci(n - 2);
     return f2.compute() + f1.join();
   }
 }
 
- ForkJoinTask:我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了两个子类:
 - RecursiveAction:用于没有返回结果的任务
 - RecursiveTask:用于有返回结果的任务
 - ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool 来执行
 - RecursiveTask:继承后可以实现递归(自己调自己)调用的任务
 
入门案例
package com.jj.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
class MyTask extends RecursiveTask<Integer>{
    //拆分差值不能超过10,计算10以内运算
    private static final Integer VALUE = 10;
    private int begin;  //拆分开始值
    private int end;    //拆分结束值
    private int result; //返回结果
    //创建有参数构造
    public MyTask(int begin,int end){
        this.begin = begin;
        this.end = end;
    }
    //拆分和合并过程
    @Override
    protected Integer compute() {
        //判断相加两个数值是否大于10
        if ((end - begin)<=VALUE){
            //相加操作
            for (int i=begin;i<=end;i++){
                result = result + i;
            }
        }else { //进一步拆分
            //获取中间值
            int middle =begin + (end - begin)/2;
            //拆分左边
            MyTask task01 = new MyTask(begin,middle);
            //拆分右边
            MyTask task02 = new MyTask(middle+1,end);
            //调用方法拆分
            task01.fork();
            task02.fork();
            //合并结果
            result = task01.join() + task02.join();
        }
        return result;
    }
}
public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyTask对象
        MyTask myTask = new MyTask(0,100);
        //创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
        //获取最终合并之后结果
        Integer result = forkJoinTask.get();
        System.out.println(result);
        //关闭池对象
        forkJoinPool.shutdown();
    }
}
 
CompletableFuture异步回调
场景:主线程里面创建一个 CompletableFuture,然后主线程调用 get 方法会 阻塞,最后我们在一个子线程中使其终止。
package com.jj.completable;
import java.util.concurrent.CompletableFuture;
//异步调用和同步调用
public class CompletableFutureDemo {
    public static void main(String[] args) throws Exception {
        //同步调用
        CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName()+" : CompletableFuture1");
        });
        completableFuture1.get();
        //mq消息队列
        //异步调用
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+" : CompletableFuture2");
            //模拟异常
            int i = 10/0;
            
            return 1024;
        });
        completableFuture2.whenComplete((t,u)->{
            System.out.println("------t="+t);
            System.out.println("------u="+u);
        }).get();
    }
}










