Day9 Juc并发编程

几个概念

  • 进程:程序执行的一个过程(从创建到消亡,一个软件的运行就可以认为是一个进程)
  • 线程:与进程类似,比进程更下的执行单位,一个进程可以包含多个线程,多个线程共享进程的堆和方法区的资源
  • 并发:多个线程/进程在同一时间段内执行(可以看成是一堆人去某个地方,没有先后顺序)
  • 并行:多个线程/进程在同一时间点执行(一堆人排成一排同时去某个地方)
  • 同步:调用方法一旦开始,就必须等待调用方法返回后,才能执行后续的操作
  • 异步:调用方法,不用等待返回,直接执行后续的操作,当方法结束后,发一个通知给当前线程
  • 阻塞:调用结果还没有返回之前,线程阻塞挂起,得到结果时,再执行后续操作(与同步不同,阻塞是有目的性的等待)
  • 死锁:不同的线程占用了对方的资源不放,都在等待对方释放资源

JUC(java.util.concurrent):基于多线程的一种编程技术

创建多线程的几个方式

public class Chap01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // main函数也是一个线程(当启动main函数时,还会有其他的线程同时启动)
        new ThreadTest1().start();    // 启动线程
        new Thread(new ThreadTest2()).start();    // 启动线程
        // 与方式二相同
        new Thread(() -> System.out.println(Thread.currentThread().getName() + ":开始")).start();

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }

        // 创建FutureTask的对象, 此处同样可以使用匿名的操作
        // FutureTask可用于包装Callable或Runnable对象
        FutureTask futureTask = new FutureTask(new ThreadTest3());
        new Thread(futureTask).start();       // 启动线程
        // 拿到返回值,此处需要捕获异常,执行此语句时,主线程会阻塞
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName() + "主线程结束");
    }
}

// 方式一:继承Thead,重写run()方法,就是一个线程
class ThreadTest1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

// 方式二:实现Runnable接口,实现run方法
class ThreadTest2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

// 方式三,实现Callable接口,可以指定泛型,存在放回值(泛型所指定的对象)
class ThreadTest3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer num = 0;
        for (int i = 0; i < 10; i++) {
            num += i;
        }
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " 开始了");
        return num;
    }
}

线程的声明周期:六种

状态说明
NEW初始状态,调用start之前
RUNNABLE运行状态,调用start。又分为就绪和运行中
BLOCKED阻塞状态,同步锁操作
WAITING等待,sleep,wait等操作
TIMED_WAITING超时等待
TERMINATED中止,执行完成

volatilesynchronized

  • volatile:java提供的轻量级的同步机制
    • 保证可见性
    • 不保证原子性
    • 禁止指令重排
    • 只能用于变量
  • synchronized:同步锁,修饰代码块或方法
public class Chap02 {
    private static volatile int flag = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() ->{
            // 不叫volatile,数据不可见,进入循环,
            // 主线程改变了数据,但是该线程‘看不见’数据改变了,就会进入死循环,
            while (true){
                if (flag == 1){
                    System.out.println(flag);
                    break;
                }
            }
        }, "线程一").start();
        Thread.sleep(1000);     // 让主线程处于等待状态,保证线程一进入循环
        flag = 1;
        System.out.println(flag);
    }
}

wait/sleep的区别

  • 来自不同的类 wait --> Object sleep --> Thread
  • 锁的释放 wait会释放锁, sleep不会释放锁
  • 使用的地方不同 wait必须在同步代码块中使用 sleep任何地方都可以使用

Lock锁

// 买票模型
public class Chap03 {
    // Lock锁
    public static void main(String[] args) {
        // 创建三个窗口来卖票
        new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Ticket.sale();
            }
        }, "窗口一").start();
        new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Ticket.sale();
            }
        }, "窗口二").start();
        new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                Ticket.sale();
            }
        }, "窗口三").start();
    }
}

class Ticket{
    private static int num = 100;
    // 创建Lock锁
    static Lock lock = new ReentrantLock();

    public static void sale(){    // 卖票
        lock.lock();   // 上锁
        try {     // 防止异常之后不能解锁,导致死锁
            if (num > 0){
                System.out.println(Thread.currentThread().getName() + "卖出了第:" + (100-(--num)) + " 张票,剩余:" + num);
            }
        }finally {
            lock.unlock();   // 解锁
        }
    }
}

synchronizedLock的区别

  • 都是解决线程安全问题的
  • Lock是一个接口,而 synchronized 是 java 的关键字
  • synchronized在执行完对应的操作后,自动释放锁,Lock锁需要手动上锁(lock())和解锁(unlock()),为了防止死锁,需要加上try-ffinally
// Lock锁与Condition线程监视器的例子
public class Chap04 {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                data.printA();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                data.printB();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                data.printC();
            }
        }, "C").start();
    }
}

class Data2{
    // 等待,业务,通知
    private int num = 1;
    // 创建锁
    final private Lock lock = new ReentrantLock();
    //  Condition取代了对象监视器方法的使用。使线程按照一定规律来执行
    final private Condition condition1 = lock.newCondition();   // 线程a的监视器
    final private Condition condition2 = lock.newCondition();   // 线程b的监视器
    final private Condition condition3 = lock.newCondition();   // 线程c的监视器

    // num == 1时工作
    public void printA(){
        lock.lock();   // 上锁
        try{
            while (num != 1){
                condition1.await();   // 等待
            }
            num = 2;
            System.out.println(Thread.currentThread().getName() + ": " + num);
            condition2.signal();      // 唤醒b
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();    // 解锁
        }
    }

    // num == 2时工作
    public void printB(){
        lock.lock();
        try{
            while (num != 2){
                condition2.await();
            }
            num = 3;
            System.out.println(Thread.currentThread().getName() + ": " + num);
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }

    // num == 3时工作
    public void printC(){
        lock.lock();
        try{
            while (num != 3){
                condition3.await();
            }
            num = 1;
            System.out.println(Thread.currentThread().getName() + ": " + num);
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}

说明:其中的await()signal()对应使用synchronized时的wait()notify()

生产者消费者问题可以使用上面类似的思路

集合的线程安全

// 异常情况
public class Chap05 {
    // 集合的线程安全操作
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {     // 创建20个线程往集合中添加数据
            int finalI = i;
            new Thread(() -> {
                list.add(finalI);
                System.out.println(list);       // java.util.ConcurrentModificationException
            }, String.valueOf(i)).start();
        }
    }
}
public class Chap05 {
    // 集合的线程安全操作
    public static void main(String[] args) {
//        List<Integer> list = new ArrayList<>();
        // 解决方式一,使用线程安全的Vector集合类
//        List<Integer> list = new Vector<>();
        // 方法二,使用Collections工具类,底层与方式一相同
//        List<Integer> list = Collections.synchronizedList(new ArrayList<>());
        // 方法三,使用juc,底层使用的是CopyOnWrite,写入时复制,使用的是Lock锁,效率更高
        List<Integer> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 20; i++) {     // 创建20个线程往集合中添加数据
            int finalI = i;
            new Thread(() -> {
                list.add(finalI);
                System.out.println(list);       // java.util.ConcurrentModificationException
            }, String.valueOf(i)).start();
        }
    }
}

mapset的操作类似,都可以使用CopyOnWrite

List<Integer> list = new CopyOnWriteArrayList<>();
Map<String, String> map = new ConcurrentHashMap<>();
Set<String> set = new CopyOnWriteArraySet<>();

CopyOnWrite是如何做到的:

  • add,put等添加操作,底层会创建一个原数组的副本进行写入,然后再将修改完的副本替换为原来的数据,实现读与写的互不影响
  • 效率高:是因为读取操作不存在同步或锁操作,内部数据的改变只能通过副本替换,可以保证数据的安全
  • 添加时,使用的是Lock锁

ThreadLocal:规避线程不安全的另一种方法

  • 提供线程的本地变量,当创建了一个ThreadLocal变量,那么访问这个变量的每一个线程都会拷贝一份自己的副本
  • 底层原理:当调用set的时候,会将当前线程与该值绑定,使用ThreadLocalMap,将该线程作为建,将set的内容作为值存储起来,
public class Chap06 {
    static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set(0);    // 主线程
        new Thread(() -> {
            threadLocal.set(1);     // 添加值
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());  // 1
        }, "线程一").start();
        new Thread(() -> {
            threadLocal.set(2);     // 添加值
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());   // 2
        }, "线程二").start();
        System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());  // 0
    }
}

读写锁(ReadWriteLock)

public class Chap09 {
    // 读写锁(接口):读的时候可以被多个线程读取,写的时候只能一个线程操作
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        // 写入
        for (int i = 1; i <= 6; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 6; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

class MyCache{
    private volatile Map<String, String> map = new HashMap<>();

    //    Lock lock = new ReentrantLock();
    // 读写锁
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    // 写
    public void put(String key, String value){
//        lock.lock();
        readWriteLock.writeLock().lock();    // 读锁
        System.out.println(Thread.currentThread().getName() + ",写入:" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + ",写入完成");
//        lock.unlock();
        readWriteLock.writeLock().unlock();
    }

    // 读
    public void get(String key){
        readWriteLock.readLock().lock();    // 写锁
        System.out.println(Thread.currentThread().getName() + ",读取:" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + ",读取完成");
        readWriteLock.readLock().unlock();
    }
}

线程池: 别人建好的线程,放在线程池,当我们需要的时候去拿,用完还回去

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
  • 三大方法,七大参数,四种拒绝策略
// 使用Executors创建线程池,开发中禁止这样使用(浪费资源)
public class Chap07 {
    // 线程池
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();// 可变大小的线程池
        System.out.println(Runtime.getRuntime().availableProcessors());  // 获取cup核数
        // 创建指定大小的线程池
        ExecutorService executorService1 = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        try{
            for (int i = 0; i < 20; i++) {
                int finalI = i;    // 使用线程池创建线程
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + ": " + finalI);
                });
            }
        }finally {
            executorService.shutdown();    // 执行完成之后,需要关闭线程池(把线程还给线程池)
        }
    }
}
// 应该使用这种方式创建线程池
public class Chap08 {
     /*      源码
    public ThreadPoolExecutor(int corePoolSize,     // 核心线程数
                              int maximumPoolSize,  // 最大线程数,当缓冲区满时,才会使用最大线程
                              long keepAliveTime,   // 当核心线程没有工作到一定时间,才会销毁线程
                              TimeUnit unit,     // 时间参数的单位
                              BlockingQueue<Runnable> workQueue,   // 队列,缓冲,当到达核心线程数时,将新来的线程放入队列等待
                              ThreadFactory threadFactory,   // 生产线程的工厂(一般用默认的) Executors.defaultThreadFactory()
                              RejectedExecutionHandler handler) {     // 拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
        四大拒绝策略(以银行为例)
            new ThreadPoolExecutor.AbortPolicy()     // 窗口和休息区都满了,不处理这个人,抛异常,默认使用
            new ThreadPoolExecutor.CallerRunsPolicy()     // 哪里来的去哪里
            new ThreadPoolExecutor.DiscardPolicy()     // 丢掉任务,不抛异常
            new ThreadPoolExecutor.DiscardOldestPolicy()     // 尝试和最早的竞争,不抛异常
     */

    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                5, 
                Runtime.getRuntime().availableProcessors(),  // cup核数
                2,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try{
            for (int i = 0; i < 20; i++) {
                int finalI = i;
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + ": " + finalI);
                });
            }
        }finally {
            executorService.shutdown();
        }
    }
}

AQS:AbstractQuenedSynchronizer

AQS的核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

BlockingQueue(阻塞队列)

public class BlockingQueueTest {
    /*
    什么时候会用到阻塞队列
        多线程并发处理,线程池
        队列的操作   (添加,移除,生产者消费者模型)
     */
    public static void main(String[] args) {
//        test1();
//        test2();
//        test3();
        test4();
    }
    
    // 抛异常的情况  add()/remove()
    public static void test1(){
        // 创建队列,并设置队列的大小
        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());    // 检查队首的元素
        // Queue full
//        System.out.println(blockingQueue.add("d"));
        System.out.println("+++++++++++++++++++=");
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        // java.util.NoSuchElementException
//        System.out.println(blockingQueue.remove());
    }

    // 不抛异常的情况   offer()/poll()
    public static void test2(){
        BlockingQueue<String> 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.offer("d"));    // false
        System.out.println(blockingQueue.peek());     // 检查队首的元素
        System.out.println("_______________----");
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());   // null
    }

    // 等待阻塞    put()/ take()
    public static void test3(){
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        try {
            blockingQueue.put("a");
            blockingQueue.put("b");
            blockingQueue.put("c");
            // 阻塞
//            blockingQueue.put("c");
            blockingQueue.take();
            blockingQueue.take();
            blockingQueue.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 超时等待
    public static void test4(){
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        try {
            System.out.println(blockingQueue.offer("a", 2, TimeUnit.SECONDS));
            System.out.println(blockingQueue.offer("b", 2, TimeUnit.SECONDS));
            System.out.println(blockingQueue.offer("c", 2, TimeUnit.SECONDS));
            //
//            System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));
            System.out.println("+++++++++++++++++++");
            System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
            System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
            System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 四组API(当队列满/空时,有不同的策略)
方法(添加/取出)处理方式
add()/remove()抛出异常
offer()/poll() 无参不抛异常
put()/take()等待阻塞
offer()/poll() 多了两个参数:超时时间和单位超时等待

Semaphore(信号量)

synchronizedReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。

public class Chap10 {
    // Semaphore(信号量)
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(50);   // 创建指定大小的线程池
        Semaphore semaphore = new Semaphore(5);     // 创建许可
        try{
            for (int i = 0; i < 100; i++) {
                int finalI = i;
                semaphore.acquire();     // 获得许可(可以执行的线程数量为:5/1 = 5)
                executorService.execute(() -> System.out.println(Thread.currentThread().getName() + ": " + finalI));
                semaphore.release();    // 释放许可
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }
    }
}
  • 创建许可时,可以指定第二个参数,是否为公平模式,默认是非公平模式
  • 获得和释放许可时,可以指定参数,表示获取和释放许可的数量

CountDownLatch(减法计数器)

public class Chap11 {
    //CountDownLatch()
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        CountDownLatch countDownLatch = new CountDownLatch(50);    // 总数为50,必须要执行任务的时候使用
        try{
            for (int i = 0; i < 50; i++) {
                int finalI = i;
                executorService.execute(() -> System.out.println(Thread.currentThread().getName() + ": " + finalI));
                countDownLatch.countDown();     // 每次执行一条线程,计数减一
            }
        }finally {
            executorService.shutdown();
        }
        countDownLatch.await();     // 等待计数器归零,然后再继续往后执行
        System.out.println("倒数完成,后续的主线程开始执行");
    }
}
  • 一个过多个线程需要等待前面n个线程执行完成后才执行,可以使用
  • 多个线程有并行需求的时候,可以使用(计数完成,多条线程同时执行)
  • 如果控制不当,可能会造成死锁,导致后续线程无法执行

CyclicBarrier: 加法计数器

public class Chap12 {
    public static void main(String[] args) {
        // CyclicBarrier: 加法计数器
        // 每次执行完一条线程之后,将计数加一,当计数为规定值的时候,执行指定的某个操作
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("成功召唤神龙"));
        for (int i = 1; i < 8; i++) {
            int finalI = i;
            executorService.execute(() -> {
                System.out.println("拿到第" + finalI + "颗龙珠");
                try {
                    cyclicBarrier.await();     // 等待,有一个线程经过,计数加一
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}
  • 每次计数执行完操作之后,会将计数器重置,可以多次使用
  • 多个线程,在任意一个线程没有完成,所有线程都必须等待
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值