JUC笔记(下)

7 Callable接口

runnable 与callable的区别

相同点:
1.两个都是接口
2.两者都可以用来编写多线程程序
3.两者都可以调用Thread.start()来启动线程
不同点:
1.Runnable提供run(),无法通过throws抛出异常,所有CheckedException必须在run()内部处理;Callable提供call方法,直接抛出Exception异常。
2.Runnable的run无返回值,Callable的call方法提供返回值用来表示任务运行的结果
3.Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行。而Callable只能通过线程池执行。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @Author: mei_ming
 * @DateTime: 2022/5/29 14:42
 * @Description: runnable 与callable的区别
 */
class MyThread1 implements Runnable{

    @Override
    public void run() {

    }
}
class MyThread2 implements Callable{

    @Override
    public Integer call() throws Exception {
        return 200;
    }
}
public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Runnable方式
        new Thread(new MyThread1(),"AA").start();

        //FutureTask
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        //lam表达式
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName()+" come in callable");
            return 1024;
        });
        new Thread(futureTask2,"luck").start();
        new Thread(futureTask1,"mary").start();
//        while(!futureTask2.isDone()){
//            System.out.println("wait......");
//        }
        //调用FutureTask的get()
        System.out.println(futureTask2.get());
        System.out.println(futureTask1.get());
        //System.out.println(futureTask2.get()); //第二次,直接输出第一次执行结果
        System.out.println(Thread.currentThread().getName()+" come over");
    }
}

8. JUC强大的辅助类

8.1 减少计数CountDownLatch

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

CountDownLatch主要有2个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
其它线程调用countDown方法会将计数器减1
当计数器为0时,await方法会被唤醒,执行


package ming.xuexi.juc;

import java.util.concurrent.CountDownLatch;

/**
 * @Author: mei_ming
 * @DateTime: 2022/5/29 15:11
 * @Description: 6个同学走后,班长才关门
 */

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //改进,创建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();
        }

        //改进  等待
        countDownLatch.await();
        System.out.println("班长锁门走人....");
    }
}

8.2 循环栅栏CyclicBarrier

CyclicBarrier 循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数会加1,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为+1操作

package ming.xuexi.juc;

import java.util.concurrent.CyclicBarrier;

/**
 * @Author: mei_ming
 * @DateTime: 2022/5/29 15:25
 * @Description: 集齐7颗龙珠,可召唤神龙
 */

public class CyclicBarrierDemo {
    private static int num=7;

    public static void main(String[] args) {
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
            System.out.println("*******成功集齐7颗龙珠********");
        });

        //集齐龙珠过程
        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                try{
                    System.out.println(Thread.currentThread().getName()+"星完成");
                    //等待
                    cyclicBarrier.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

8.3 信号灯Semaphore

Semaphore的构造方法中传入的第一个参数是最大信号量,每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获取许可证,release方法释放许可。

package ming.xuexi.juc;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @Author: mei_ming
 * @DateTime: 2022/5/29 15:40
 * @Description: 6辆汽车,停3个停车位
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆车
        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();
        }
    }
}

9. ReentrantReadWriteLock 读写锁

乐观锁与悲观锁

悲观锁每次去拿数据的时候,都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实多是提供的乐观锁。在Java中Java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

表锁和行锁

MyISAM不支持行锁,InnoDB支持行锁和表锁。
在Mysql的InnoDB引擎支持行锁,MySQL的行锁是通过索引加载的,即是行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描,行锁则无法实现,取而代之的是表锁。

表锁 不会出现死锁,发生锁冲突几率高,并发低
表锁 会出现死锁,发生锁冲突几率低,并发高

共享锁和排它锁

行锁分为共享锁和排他锁

共享锁又称:读锁。当一个事务对某几行上读锁时,允许其它事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。(会产生死锁)
排它锁又称:写锁。当一个事务对某几行上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。(会产生死锁)

ReadWriteLock 接口

实现类 ReentrantReadWriteLock
里面有两个静态内部类

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable{
    //读锁        
    public static class ReadLock implements Lock, java.io.Serializable{}
    //写锁      
    public static class WriteLock implements Lock, java.io.Serializable{}
}

package ming.xuexi.readwrite;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Author: mei_ming
 * @DateTime: 2022/6/1 14:53
 * @Description: TODO
 */
//资源类
class MyCache{

    //创建map集合
    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);
            TimeUnit.MICROSECONDS.sleep(300);
            //放数据
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+" 写完了..");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwLock.writeLock().unlock();
        }
    }
    //取数据
    public Object get(String key){
        //创建读锁
        rwLock.readLock().lock();
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName()+" 正在读取数据.."+key);
            TimeUnit.MICROSECONDS.sleep(300);
            //取数据
            result= map.get(key);
            System.out.println(Thread.currentThread().getName()+" 取完了..");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwLock.readLock().unlock();
        }
        return result;
    }
}
public class ReadWriteLockDemo {
    public static void main(String[] args) {

        MyCache myCache = new MyCache();
        //创建线程,放数据
        for (int i = 1; i <=5 ; i++) {
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        //创建线程,取数据
        for (int i = 1; i <=5 ; i++) {
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

10. BlockingQueue阻塞队列

10.1 阻塞队列的概述

Concurrent包中,阻塞队列很好的解决了多线程中,如何高效安全“传输”数据的问题,通过这些高效并线程安全的队列类,为我们快速搭建高质量的多线程程序带来了极大的便利。

阻塞队列,是一个队列,通过共享的队列,可以使得数据由队列的一端输入,从另外一端输出

当队列是空的,从队列中获取元素的操作将会被阻塞。
当队列是满的,从队列中添加元素的操作将会被阻塞。
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。
试图向已满的队列中添加元素的线程会被阻塞。直到其他线程移除一个元素。

10.2 阻塞队列的框架

BlockingQueue 接口,
其实现类:ArrayBlockingQueue、LinkedBlockingQueue

10.3 阻塞队列分类

ArrayBlockingQueue:由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界的(默认大小为integer.max)阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列

10.4 阻塞队列核心方法

方法类型 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用

  1. 抛出异常:
    当阻塞队列满时,在往里面加元素就会抛IllegalStateException:Queue full
    当阻塞队列空时,在往里面取元素就会抛NoSuchElementException

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue 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());//return the head of this queue
        //System.out.println(blockingQueue.add("w"));//java.lang.IllegalStateException: Queue full
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //System.out.println(blockingQueue.remove());//java.util.NoSuchElementException
    }
}

  1. 特殊值:
    插入成功,返回true,失败返回false
    移除成功,返回出队列的元素,失败返回null
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) {

        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //System.out.println(blockingQueue.peek());//return the head of this queue
        //System.out.println(blockingQueue.offer("w"));//false
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());//null
    }
}

  1. 阻塞:
    当阻塞队列满时,在往里面加元素会一直阻塞到其他线程取出元素后,在插入
    当阻塞队列空时,在往里面取元素会一直阻塞到其他线程插入元素后,在取出
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //blockingQueue.put("d");  //处于阻塞等待状态中
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //System.out.println(blockingQueue.take()); //处于阻塞等待状态中
    }
}

  1. 超时:
    当阻塞队列满时,在往里面加元素会阻塞到一定时间,还是没有其他线程取出元素,则退出
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 {
        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        //blockingQueue.offer("d",3L, TimeUnit.SECONDS);//处于阻塞等待状态中 阻塞3s
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS)); //处于阻塞等待状态中 返回null
    }
}

11 ThreadPool线程池

11.1 线程池概述

线程池:一种线程使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理着分配可并发执行的任务。避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

优点:
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等待,等其他线程执行完毕,再从队列中取出任务来执行。

主要特点:
降低资源消耗:
提高响应速度:
提高线程可管理性:

11.2 线程池架构

Executor : 总接口
Executors:工具类
ExecutorService :Executor的子接口
ThreadPoolExecutor : AbstractExecutor接口的实现类

11.3 线程池使用方式

  1. 一池N线程
    Executors.newFixedThreadPool(int)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        ExecutorService executorService01 = Executors.newFixedThreadPool(5);
        try{
            for (int i = 1; i <=10; i++) {
                //执行
                executorService01.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭
            executorService01.shutdown();
        }
    }
}
/*
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-5 办理业务
pool-1-thread-4 办理业务
*/
  1. 一个任务一个任务的执行,一池一线程
    Executors.newSingleThreadExecutor()
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        ExecutorService executorService02 = Executors.newSingleThreadExecutor();
        try{
            for (int i = 1; i <=10; i++) {
                //执行
                executorService02.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭
            executorService02.shutdown();
        }
    }
}
/*
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
*/

  1. 线程池根据需要创建线程,可扩容
    Executors.newCachedThreadPool()
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        ExecutorService executorService03 = Executors.newCachedThreadPool();
        try{
            for (int i = 1; i <=10; i++) {
                //执行
                executorService03.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭
            executorService03.shutdown();
        }

    }
}

/*
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-4 办理业务
pool-1-thread-5 办理业务
pool-1-thread-6 办理业务
pool-1-thread-7 办理业务
pool-1-thread-8 办理业务
pool-1-thread-10 办理业务
pool-1-thread-9 办理业务
*/

11.4 线程池底层原理

上面3个创建线程池,底层都是ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
            int maximumPoolSize,
            long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory,
            RejectedExecutionHandler handler){}

11.5 线程池7个参数

int corePoolSize : 常驻线程数量(核心)
int maximumPoolSize : 最大线程数量
long keepAliveTime,TimeUnit unit : 线程存活时间
BlockingQueue workQueue : 阻塞队列
ThreadFactory threadFactory : 线程工厂
RejectedExecutionHandler handler: 拒绝策略

11.6 线程池底层工作流程

拒绝策略:

  1. AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException;
  2. CallerRunsPolicy: 直接在execute方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被抛弃。
  3. DiscardPolicy:直接抛弃任务
  4. DiscardOldestPolicy:抛弃队列中等待时间最长的任务。

工作流程:

  1. 执行execute()时,线程池判断核心线程池里的核心线程是否都在执行。如果不是,让空闲的核心线程来执行任务,如果核心线程池里的线程都在执行任务,则进入下个流程。
  2. 线程池判断阻塞队列是否已满,如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中,如果阻塞队列已满,则进入下一个流程。
  3. 线程池判断线程池里的线程数量是否小于最大线程数量,如果小于,则创建一个新的工作线程(非核心,设置超时时间)来执行任务,如果已满,则交给拒绝策略来处理这个任务。

11.7 自定义线程池

一般开发中使用ThreadPoolExecutor自定义线程池


import java.util.concurrent.*;

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                8,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try{
            for (int i = 1; i <=10; i++) {
                //执行
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭
            threadPool.shutdown();
        }
    }
}

12 . Fork/Join分支合并框架

Fork : 把一个复杂任务进行拆分,大事化小
Join : 把拆分任务的结果进行合并

个人学习笔记,欢迎纠错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值