Java进阶内容(一)--并发编程(2)

并发编程(2)

线程之间的协作(等待和唤醒机制)–生产者消费者模型

  • 对于消费者要消费产品时,可以使用轮询的方式对产品进行判断其是否存在,存在则进行消费,这种方式的弊端就是,实时性查,性能开销大;使用等待唤醒机制可以解决此弊端

  • 等待唤醒是基于类的方法obj.wait()和obj.notify(),调用此方法之前都必须持有锁

    public final void wait() throws InterruptedException
    public final void notify()
    public final void notifyAll()
    
  • notifyAll()可以唤醒使用此类调用wait()方法的线程

  • notify()在源码的注释中说到notify选择唤醒的线程是任意的,但是依赖于具体实现的jvm;而hotspot对notofy()的实现并不是我们以为的随机唤醒, 而是“先进先出”的顺序唤醒!

    参考链接:https://www.jianshu.com/p/3bba64487922

等待唤醒机制的范式

  • 生产者

    1. 获取容器的锁
    2. 当商品小于最小数量则生产商品放入容器
    3. 当商品数量大于设定值则唤醒消费者
    4. 当商品大于最大数量则停止生产
  • 消费者

    1. 获取容器的锁
    2. 当商品数量小于设定值则停止消费进行等待
    3. 被唤醒时继续进行消费
import java.util.LinkedList;

/**
 * 简单实现生产者消费者模型
 *
 * @author maolin yuan
 * @version 1.0
 * @date 2021/8/27 16:00
 */
public class ProducerConsumer {

    private static final LinkedList<Integer> INTEGERS = new LinkedList<>();

    private static final int MID = 25;

    private static final int MIN = 3;

    private static final int MAX = 50;

    static class Producer implements Runnable {

        int i = 0;


        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                synchronized (INTEGERS){
                    // 当商品数量小于设定的最小值时进行生产
                    if (INTEGERS.size() < MIN){
                        System.out.println("生产者进行生产。。");
                        while (INTEGERS.size() < MAX){
                            INTEGERS.add(i);
                            i++;
                            if (INTEGERS.size() == MID){
                                System.out.println("生产者唤醒消费者进行消费。。");
                                INTEGERS.notifyAll();
                            }
                        }
                    }
                    if (INTEGERS.size() >= MAX){
                        System.out.println("生产者等待。。");
                        try {
                            INTEGERS.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                }
            }
        }

    }

    static class Consumer implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                synchronized (INTEGERS){
                    if (INTEGERS.size() == MID){
                        System.out.println("商品数量低于设定值唤醒生产者。。");
                        INTEGERS.notifyAll();

                    }
                    if (INTEGERS.size() < MIN){
                        System.out.println("商品卖完了消费者等待并唤醒生产者。。");
                        INTEGERS.notifyAll();
                        try {
                            INTEGERS.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Integer first = INTEGERS.removeFirst();
                    System.out.println(first);
                }
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {

        Thread pro  =new Thread(new Producer());
        pro.setDaemon(true);
        pro.start();


        Thread con  =new Thread(new Consumer());
        con.setDaemon(true);
        con.start();

        Thread.sleep(50);
    }
}

等待超时模式

  • 定义一个超时时间,等待的线程超过这个时间会继续执行返回一个默认结果
// 设定超时时间为T
long overtime = System.currentTimeMillis() + T;
long remain = T;
while(result 不满足条件 && remain > 0){
    // 等待固定时间
    wait(T);
    // 被唤醒之后还不满足条件,更新remain
    remain = overtime - System.currentTimeMillis();
}
return result;

join()方法使别的线程插队

public final void join() throws InterruptedException
  • 在线程中调用别的线程的join()方法,此线程会阻塞,等待别的线程执行完才会执行
/**
 * 测试线程join方法
 * @author maolin yuan
 * @version 1.0
 * @date 2021/8/27 17:40
 */
public class ThreadJoin {

    static class ThreadDemo implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("demo线程执行完成");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main 开始执行");
        Thread demo = new Thread(new ThreadDemo());
        demo.start();
        demo.join();
        System.out.println("main 完成执行");
    }

}
// main 开始执行
// demo线程执行完成
// main 完成执行

线程调用yield()、sleep()、wait()、notify()方法对锁的影响

  • yield()、sleep()方法都不会释放锁
  • 调用yield()方法释放了执行权之后cpu还是有可能会选择它,调用sleep()的线程cup不会选择它
  • 只能在持有锁的情况下调用wait()方法,并且只能用锁调用wait()方法,调用wait()方法之后会立即释放锁,当wait()方法返回(被唤醒)之后会重新持有锁
  • 只能在持有锁的情况下调用notify()方法,并且只能用锁调用notify()方法,调用notify()之后不会立即释放锁,只有当持有锁的代码块执行完之后才会释放锁

Java的并发工具类(Fork-Join)

  • 分而治之的编程思想,讲一个问题分解成若干个相互独立小问题,再把所有小问题的解合并起来得到最终的解(通过递归和多线程实现),例如规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解

fork-join

假如相加一个长度为N的数组中所有的元素,可以把数组分为K份,分别计算每一份的结果,最后把结果相加得到最终结果

  • 工作密取(workStealing)指当多个线程同时执行一系列的任务时,任意一个线程执行完成之后,会帮助执行其他线程的任务,使线程被最大限度利用起来

工作密取

  • Fork-Join标准使用范式

fork-join标准使用范式

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * 使用fork-join同步执行并返回
 * @author maolin yuan
 * @version 1.0
 * @date 2021/8/30 10:42
 */
public class ForkJoinTest {

    static class MyTask extends RecursiveTask<Long> {

        // 需要计算的数组
        int[] ints;

        // 开始的索引
        int start;

        // 结束索引
        int end;

        // 设定的阈值(固定值)
        int sdz;

        public MyTask(int[] ints, int start, int end, int sdz) {
            this.ints = ints;
            this.start = start;
            this.end = end;
            this.sdz = sdz;
        }

        /**
         * The main computation performed by this task.
         *
         * @return the result of the computation
         */
        @Override
        protected Long compute() {
            if ((end - start) < sdz) {
                // 长度小于设定值时直接计算结果
                long count = 0;
                for (int j = start; j < end; j++) {
                    try {
                        // 增加执行时间
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count = count + ints[j];
                }
                return count;
            } else {
                // 长度大于设定值时拆分任务(可以拆分两个或者多个)
                // 计算中间位置的索引
                int i = (end + start) / 2;
                MyTask myTask1 = new MyTask(ints, start, i, sdz);
                MyTask myTask2 = new MyTask(ints, i, end, sdz);
                // 执行所有拆分的子任务
                invokeAll(myTask1, myTask2);
                // 返回的结果是所有任务相加
                return myTask1.join() + myTask2.join();
            }

        }
    }

    public static void main(String[] args) {
        int aa = 5000;
        int[] ints = new int[aa];
        for (int i = 0; i < aa; i++) {
            ints[i] = (int) (Math.random() * 100);
        }

        // 使用Fork-Join
        long time = System.currentTimeMillis();
        // 创建线程池
        ForkJoinPool pool = new ForkJoinPool();
        // 创建任务
        MyTask myTask = new MyTask(ints, 0, ints.length, aa / 10);
        // 使用线程池执行任务
        pool.invoke(myTask);
        // 获取任务结果
        Long join = myTask.join();
        System.out.println("结果是:" + join);
        System.out.println("时间是:" + (System.currentTimeMillis() - time));

        // 不使用Fork-Join
        time = System.currentTimeMillis();
        long l = 0;
        for (int anInt : ints) {
            try {
                // 增加执行时间
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            l = l + anInt;
        }
        System.out.println("结果是:" + l);
        System.out.println("时间是:" + (System.currentTimeMillis() - time));
    }
}

// 结果是:248063
// 时间是:1239
// 结果是:248063
// 时间是:8143

当注释掉增加执行时间的代码时,为什么使用Fork-Join时间还反而变长了,是因为线程上下文切换耗费了大部分时间,而计算结果的时间只占很少一部分。

  • 使用Fork-Join查找异步文件
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

/**
 * 使用fork-join异步查找yml结尾的文件
 * 子任务不确定数量
 * @author maolin yuan
 * @version 1.0
 * @date 2021/8/30 15:05
 */
public class FindFiles {

    static class Find extends RecursiveAction {

        private final File file;

        public Find(File file) {
            this.file = file;
        }

        /**
         * The main computation performed by this task.
         */
        @Override
        protected void compute() {
            List<Find> ff = new ArrayList<>();
            if (file.exists()) {
                File[] files = file.listFiles();
                assert files != null;
                if (files.length != 0) {
                    for (File f : files) {
                        if (f.isDirectory()) {
                            // 如果file是文件夹则创建子任务
                            ff.add(new Find(f));
                        } else if (f.getAbsolutePath().endsWith("yml")) {
                            System.out.println("YML文件:" + f.getAbsolutePath());
                        }
                    }
                    if (!ff.isEmpty()){
                        for (Find find : invokeAll(ff)) {
                            find.join();
                        }
                    }
                }

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ForkJoinPool pool = new ForkJoinPool();
        Find task = new Find(new File("I:\\MyFile"));
        // 异步执行使用execute
        pool.execute(task);
        task.join();
        // Thread.sleep(2000);
    }

}

常用的其他并发工具类

  1. CountDownLatch(闭锁):创建一个计数器,指定数值,调用await()方法可以阻塞当前线程,其他线程调用countDown()方法使数值减一,当数值为0时,被阻塞的方法将继续执行,因此可以控制线程执行的先后顺序
  2. CyclicBarrier(栅栏):创建一个栅栏,指定数值,任意线程会在调用await()的地方被阻塞,只有当所有的线程都到达await()的地方时,阻塞才放开,让所有被阻塞的线程继续执行;也可以在构造方法中传入一个runnable,使阻塞放开之后执行此任务
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

/**
 * 使用CountDownLatch
 * @author maolin yuan
 * @version 1.0
 * @date 2021/8/30 15:41
 */
public class UseConcurrentUtil {

    private static final CountDownLatch LATCH = new CountDownLatch(3);

    private static final CyclicBarrier BARRIER = new CyclicBarrier(4, new Runnable() {
        @Override
        public void run() {
            System.out.println("所有线程等待结束");
        }
    });

    static class ThreadTest extends Thread {

        @Override
        public void run() {
            System.out.println(getName() + "线程开始执行");
            try {
                Thread.sleep((int) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LATCH.countDown();
            System.out.println(getName() + "线程执行完成");
        }
    }

    static class BarrierTest extends Thread {

        @Override
        public void run() {
            System.out.println(getName() + "线程开始执行");
            try {
                Thread.sleep((int) (Math.random() * 1000));
                BARRIER.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "线程执行完成");
        }
    }

    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        System.out.println("主线程开始等待");
        for (int i = 0; i < 3; i++) {
            new ThreadTest().start();
        }
        LATCH.await();
        System.out.println("主线程继续执行");
        System.out.println("===============");
        System.out.println("BARRIER测试开始");
        for (int i = 0; i < 3; i++) {
            new BarrierTest().start();
        }
        BARRIER.await();
        Thread.sleep(1000);
        System.out.println("BARRIER测试完成");
    }

}
// 主线程开始等待
// Thread-0线程开始执行
// Thread-1线程开始执行
// Thread-2线程开始执行
// Thread-0线程执行完成
// Thread-1线程执行完成
// Thread-2线程执行完成
// 主线程继续执行
// ===============
// BARRIER测试开始
// Thread-4线程开始执行
// Thread-3线程开始执行
// Thread-5线程开始执行
// 所有线程等待结束
// Thread-4线程执行完成
// Thread-3线程执行完成
// Thread-5线程执行完成
// BARRIER测试完成
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值