JUC并发编程

本文详细介绍了Java并发编程中的重要概念,包括JUC工具包、线程状态、wait/sleep的区别、并发与并行、线程池、不同类型的锁、死锁预防、Fork/Join框架以及CompletableFuture的异步回调。涵盖了从基础原理到实践应用的全面内容。
摘要由CSDN通过智能技术生成

JUC概述

JUC是java.util.concurrent工具包的简称,是一个处理线程的工具包。

进程和线程

进程:系统中正在运行的一个应用程序,程序一但运行就是进程,进程是资源分配的最小单位。

线程:系统分配处理器时间资源的基本单元,进程之间独立执行的一个单元执行流。线程是程序执行的最小单位。

线程状态

NEW 新建

RUNNABLE 就绪

BLOCKED 阻塞

WAITING 等待

TIME_WAITING 过时不候

TERMINATED 终止

wait/sleep的区别

sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能使用

sleep不会释放锁,也不需要占用锁。wait会释放锁,调用的前提是当前线程占有锁(代码在synchronized中)

都可以被interrupted方法中断

并发和并行

并发:同一时刻多个线程在访问同一资源

并行:多项工作同时进行,然后再汇总

管程 (Monitor 监视器)

是同步机制,保证同一时间,只有一个线程访问代码、数据。

jvm同步是基于进入和退出,使用管理对象实现的Monitor来对临界区进行加锁和解锁。

用户线程和守护线程

用户线程:自定义线程。主线程结束,用户线程还在运行的话,jvm存活。

守护线程:运行在后台,例如垃圾回收。没有用户线程,主线程结束的话,jvm结束。

Lock接口

synchronized关键字

synchronized是java中的关键字,是一种同步锁,修饰的对象有一下几种。

1、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是{},作用的对象是调用这个代码块的对象。

(对于同步方法块,锁是synchronized括号里配置的对象)

2、修饰一个方法,被修饰的方法称为同步方法,其作用是整个方法,作用的对象是调用整个方法的对象。

(对于普通同步方法,锁是当前实例对象)

3、修饰一个静态方法,作用的范围是整个静态方法,作用的对象是类的所有对象。

(对于静态同步方法,锁是当前类的Class对象)

4、修饰一个类,作用范围是synchronized后面括号括起来的部分,作用对象是类的所有对象。

创建线程

1、继承Thread类(少用)

2、实现Runnable接口

// 方法一:
new Thread(new Runnable() {
    @Override
    public void run() {
    // 执行代码
    }
},"AA").start();

// 方法二:
new Thread(() -> {
    //执行代码
},"BB").start();

3、使用Callable接口

如果使用runnable创建的线程,当run完成后,无法获得线程返回的结果。因此提供了Callable接口。

Class MyThread1 implements Runnable{
    @Override
    public void run() {
    }
}


Class MyThread2 implements Callable{
    @Override
    public Object call() throw Exception{
        return null;
    }
}


public class Demo1
    public static void main(String[] args){
        new Thread(new MyThread1(),"AA").start();
        // Runnable接口有实现类FutureTask,FutureTask构造可以传递Callable
        FutureTask<Object> futureTask1 = new FutureTask<>(new MyThread2());
        // 通过lam表达式简化
        FutureTask<Object> futureTask2 = new FutureTask<>( () -> {
            return null;
        });

        // 创建线程
        new Thread(futureTask2,"nana").start();
        while (!futureTask2.isDone()){
            sout("waiting....")
        }
        // 调用FutureTask的get方法
        sout(futureTask2.get());
    }
}

4、使用线程池

Lock与Synchronized区别

1、Lock不是java语言内置的,synchronized是java语言的关键字,是内置的。Lock是一个类,通过这个类可以实现同步访问(private final ReentrantLock lock = new ReentrantLock();lock.lock(); lock.unlock(); );

2、Lock和synchronized的不同,采用synchronized不需要用户去释放锁,系统会自动让线程释放对锁的占用,而Lock必须用户去手动释放锁,如果没有主动释放锁,就有可能导致死锁现象。

3、Lock可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待线程会一直等待下去,不能够中断响应

4、通过Lock可以知道有没有成功获取锁,而synchronized无法办到

5、Lock可以提高多个线程进行读操作的效率

如果竞争资源不激烈,Lock和synchronized性能是差不多的。当竞争资源非常激烈时,Lock的性能远远优于synchronized。

多线程编程步骤

第一步:创建资源类、在资源类创建属性和操作方法(锁也在这加)

(第一步里干了三件事:1、判断(需要放到while中,否则会出现虚假唤醒)      2、干活       3、通知)

第二步:创建多个线程,调用资源类的操作方法

解决ArrayList线程不安全问题

方案一:Vector。将List<String> list = new ArrayList<>();改成List<String> list = new Vector<>();

方案二:Collections。List<String> list = Collections.synchronizedList(new ArrayList<>());

方案三:CopyOnWriteArrayList。(常用)写时复制技术。读的时候支持并发读,写的时候独立写。先复制一个和原来相同的内容,然后写入新的内容,内容写完后,与原来内容做一个合并(覆盖),后续读的时候直接读新的。好处:兼顾了并发读,还能写,避免并发修改的异常。List<String> list = new CopyOnWriteArrayList<>();

Hashset也会遇到线程不安全的问题,可以使用CopyOnWriteArraySet<>();

Hashmap也会遇到这个问题,可以使用ConcurrentHashMap<>();

多线程锁

公平锁和非公平锁

非公平锁:线程饿死,效率高

公平锁:效率相对低,但每个线程都有机会。

private final ReentrantLock lock = new ReentrantLock(true);

可重入锁

synchronized(隐式)和Lock(显示)都是可重入锁。

又称为:递归锁

最外层的锁放开后,内层的所有锁都放开。相当于回家开了大门后,卧室也可以随时去。

synchronized实现可重入锁

Object o = new Object();
new Thread(() -> {
    synchronized(o){
        // sout(外层)
        synchronized(o){
            // sout(中层)
            synchronized(o){
                // sout(内层)
            }
        }
    }
},"AA").start();

Lock实现可重入锁

Lock lock = new ReentrantLock();
new Thread( () -> {
    try{
        lock.lock();
        // sout(外层)
        try{
            lock.lock();
            // sout(内层)        
        }finally{
            
            lock.unlock();
        }
    }finally{
        lock.unlock();
    }
},"t1").start();
    

死锁

两个或两个以上的进程在执行过程中,因为争夺资源而造成一种互相等待的现象。如果没有外力干涉,他们无法再继续执行下去。

产生的原因

1、资源不足

2、进程推进顺序不合适

3、资源分配不当

JUC的辅助类

减少计数CountDownLatch

CountDownLatch类可以设置一个计数器,然后通过CountDown方法来进行减1操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。

CountDownLatch countDownLatch = new CountDownLatch(6);
countDownLatch.countDown();
countDownLatch.await();

循环栅栏CyclicBarrier

一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点。神龙等待7颗龙珠集齐才能出现许愿,然后龙珠又重新分散。

private static final int NUMBER = 7;
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
    sout("7颗集齐召唤神龙");
    });
for (int i=1;i<=7;i++){
    new Thread( () -> {
        sout(Thread.currentThread().getName() + "星龙珠找到了");
    cyclicBarrier.await();  //try
    },String.ValueOf(i)).start();
}

信号灯Semaphore

计数信号量。信号量维护了一个许可集,在许可可用前会阻塞每一个acquire,然后再获取该许可,每个release添加一个许可,从而可能释放一个正在阻塞的获取者。

6辆车,停3个车位

Semaphore semaphore = new Semaphore(3);
for(int i=1;i<=6;i++){
    new Thread( () -> {
        semaphore.acquire();  //try
        sout(Thread.currentThread().getName());
        // 设置随机停车时间
        TimeUnit.SECONDS.sleep(new Random().nextInt(5));
        semaphore.release();  //finally
    },String.ValueOf(i)).start();
}

读写锁 ReadWriteLock

一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读和写线程,读写互斥,读读共享。

缺点:

1、有可能造成锁饥饿,一直读,就没办法写,写操作进行不了

2、读的时候不能写,只有读,完成之后才能写。写操作可以读

private ReadWriteLock rwLock = new ReentrantReadWriteLock();

// 写操作前加 写锁
rwLock.writeLock().lock();
//执行写操作
rwLock.writeLock().unlock();


// 读操作前加 读锁
rwLock.readLock().lock();
// 读操作
rwLock.readLock().unlock();

演变过程

本质的过程是锁降级。将写入锁降级为读锁

jdk8中降级步骤:

获取写锁 --> 获取读锁 -->  释放写锁  -->  释放读锁

阻塞队列 BlockingQueue

队列为空,从队列中获取元素的操作会被阻塞,直到其他线程往队列中插入新的元素。

队列为满,从队列中添加元素的操作会被阻塞,直到其他线程从队列中移除一个或多个元素。

ArrayBlockingQueue(常用)

由数组结构组成的有界阻塞队列。内部维护了一个定长数组,以便缓存队列中的数据元素。内部还保存两个整型变量,分别标识着队列的头部和尾部在数组中的位置。

LinkedBlockingQueue(常用)

由链表组成的有界(大小默认为integer.MAX_VALUE)阻塞队列

DelayQueue

使用优先级队列实现的延迟无界阻塞队列。只有当其指定的延迟时间到了,才能从队列中获取到该元素。

PriorityBlockingQueue

支持优先级排序的无界阻塞队列。锁采用公平锁

SynchronousQueue

不存储元素的阻塞队列,里面只有单个元素

LinkedTransferQueue

链表组成的无界阻塞队列

LinkedBlockingDeque

链表组成的双向阻塞队列

ThreadPool线程池

线程池维护着多个线程,避免了处理短时间任务时创建与销毁线程的代价。保证了内核的充分利用,和防止过分调度。线程池的工作是控制运行的线程数量,处理过程将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了线程池有的最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

特点:

1、降低资源消耗

2、提高响应速度

3、提高线程的可管理性

线程池使用方法

创建线程的时候,一般不通过Executors来创建线程,而是通过ThreadPoolExecutor的方法来创建。为了简单才这么写。用Executor返回线程池对象可能会堆积大量的请求,导致OOM异常。

一池N线程 Executors.newFixedThreadPool(int) 

ExecutorService threadPool = Executors.newFixedThreadPool(5);
try{    
    for (int i=1;i<=10;i++){
        threadPool.execute( () -> {
            // 办理业务
        });
}catch(Exception e) {
    e.printStackTrace();
}finally{
    threadPool.shutdown();
}

一池一线程 Executors.newSingleThreadExecutor()

ExecutorService threadPool = Executors.newSingleThreadExecutor();
try{    
    for (int i=1;i<=10;i++){
        threadPool.execute( () -> {
            // 办理业务
        });
}catch(Exception e) {
    e.printStackTrace();
}finally{
    threadPool.shutdown();
}

根据需求创建线程 Executors.newCachedThreadPool()

ExecutorService threadPool = Executors.newCachedThreadPool();
try{    
    for (int i=1;i<=10;i++){
        threadPool.execute( () -> {
            // 办理业务
        });
}catch(Exception e) {
    e.printStackTrace();
}finally{
    threadPool.shutdown();
}

线程池的7个参数

int corePoolSize;   常驻线程数量(核心),例如银行固定开发窗口

int maximumPoolSize; 最大线程数量;(最多能开多少窗口)

long keepAliveTime ;线程存活时间,(临时开发的窗口多久不用就关)

TimeUnit unit,线程存活时间单位

BlockingQueue<Runnable> workQueue;阻塞队列

ThreadFactory  threadFactory;线程工厂

RejectedExecutionHandler hanler;拒绝策略,(人太多了,拒绝接纳)

ExecutorService threadPoolExecutor = new ThreadPoolExecutor{
    2,
    5,
    2L,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(3),
    Executor.defaultThreadFactory,
    new ThreadPoolExecutor.AbortPolicy()
};

threadPoolExecutor.execute( () -> {
    // 办理业务
});

线程池工作原理

只有当线程execute时,线程才会创建。当前两个线程使用完常驻线程数时,第3,4,5个线程到了会先在阻塞队列中等待,当第6,7,8个线程到时,会使用最大线程数中的其他线程。如果此时还有第9个线程到,就会使用拒绝策略。

拒绝策略

AbortPolicy(默认):直接抛出RejectExecutionException异常阻止系统正常运行

CallerRunsPolicy:"调用者运行",一种调节机制,该策略不抛弃任务,也不抛出异常。例如银行负责人说,谁让你来的,你就找谁去。

DiscardOldestPolicy:抛弃等待最久的任务,然后把当前队列中尝试再次提交任务。例如你去海底捞排队等待,已经等了一早上了,此时有新客户来,刚好空了个位,位置会让给新来的人,不要你了。

DiscardPolicy:默默丢弃无法处理的业务,不给处理也不抛出异常,如果允许任务丢失,这是最好的策略。例如去饭店吃饭,你喊老板我要吃饭,服务员和老板都不理你。

分支合并框架 Fork/Join

将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并输出。

Fork:将一个复杂任务进行拆分,大事化小

Join:把拆分任务的结果进行合并

Fork分类:

ForkJoinPool:分支合并池,类比线程池

ForkJoinTask、RecursiveTask:递归任务,继承后可以实现递归调用的任务

Class MyTask extends RecursiveTask<Integer> {
    // 拆分差值不超过10
    private static final Integer VALUE = 10;
    private int begin;
    private int end;
    private int result;

    //创建有参构造
    public MyTask(){
        this.begin = begin;
        this.end = end;
    }
        
    // 拆分和合并过程
    @Override
    protected Integer compute(){
        // 判断相加的两数是否大于10
        if (end - begin <= VALUE) {
            for (int i=begin;i<=end;i++){
                result += i;
            }
        }else{
            // 获取中间值
            int mid = (begin + end) /2;
            MyTask task1 = new MyTask(begin,mid);
            MyTask task2 = new MyTask(mid+1,end);
            task1.fork();
            task2.fork();
            result = task1.join() + task2.join();
        }
        return result;
    }
}
public class ForkJoinDemo {
    public static void main (String[] args) throws ExecutionException,InterruptedException{
        MyTask myTask = new MyTask(0,100);
        ForkJoinPool forkJoinPool -= new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
        Integer result = forkJoinTask.get();
        // sout(result);    
        forkJoinPool.shutdown();
        
    }
}

CompletableFuture异步回调

异步调用 没用返回值

CompletableFuture<Void> completableFuture = CompletableFuture.runAsync( () -> {
    //sout(Thread.currentThread().getName());
});
completableFuture.get();

异步调用 有返回值

CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync ( () -> {
    //sout(Thread.currentThread().getName())
    return 1024;
});
completableFuture.whenComplete((t,u) -> {
    // sout(t);   // 1024  return的信息
    // sout(u);    //null  异常信息
}).get();

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值