精华内容
下载资源
问答
  • java多线程
    千次阅读 多人点赞
    2022-05-01 09:49:54

    业务需求
    电影院新片首映,观影人数大量增加,为提高日营业额,线下售票窗口由原单窗口调整为3窗口,设计一段简单的程序模拟该售票过程。
    程序设计
    多线程场景下需考虑线程安全的问题,避免多个线程争抢同一个资源导致业务逻辑出现错误。实现线程安全的方式有很多,这里使用Java Lock 接口中的方法实现。
    代码示例

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 测试类
     */
    public class DemoTest {
        public static void main(String[] args) {
    
            //窗口01
            new Thread(() -> {
                while (true) {
                    //售票并获取售票后的当前票余量
                    int currentTickets = TicketSource.saleTickets();
                    //模拟售票员卖出一张票用时1秒
                    waitProcess();
                    //票已卖完
                    if (currentTickets <= 0) break;
                }
            }, "01").start();
    
            //窗口02
            new Thread(() -> {
                while (true) {
                    int currentTickets = TicketSource.saleTickets();
                    waitProcess();
                    if (currentTickets <= 0) break;
                }
            }, "02").start();
    
            //窗口03
            new Thread(() -> {
                while (true) {
                    int currentTickets = TicketSource.saleTickets();
                    waitProcess();
                    if (currentTickets <= 0) break;
                }
            }, "03").start();
    
    
        }
    
        private static void waitProcess() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    /**
     * 电影票资源类
     */
    class TicketSource {
    
        //当前电影票余量
        private static int currentTickets = 30;
    
        //加锁确保多线程场景下的线程安全
        private static final Lock lock = new ReentrantLock();
    
        /**
         * 卖票
         *
         * @return 当前电影票余量
         */
        public static int saleTickets() {
            lock.lock();
            try {
                if (currentTickets > 0) {
                    //模拟卖票
                    currentTickets--;
    
                    if (currentTickets == 0) {
                        //票余量为 0 停止售卖
                        System.out.println(
                                Thread.currentThread().getName() + "窗口出票成功!"
                                        + "当前票余量:" + currentTickets
                                        + " 今日票已卖完!");
                    } else {
                        System.out.println(
                                Thread.currentThread().getName() + "窗口出票成功!"
                                        + "当前票余量:" + currentTickets);
                    }
                } else {
                    //票余量为 0 停止售卖
                    System.out.println(Thread.currentThread().getName() + "窗口:今日票已卖完!");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
    
            return currentTickets;
        }
    
    }
    

    运行结果

    D:\installPath\Java\jdk1.8.0_121\bin\java.exe "-javaagent:D:\installPath\IntelliJ IDEA 2019.1.4\lib\idea_rt.jar=64339:D:\installPath\IntelliJ IDEA 2019.1.4\bin"
    01窗口出票成功!当前票余量:29
    02窗口出票成功!当前票余量:28
    03窗口出票成功!当前票余量:27
    01窗口出票成功!当前票余量:26
    03窗口出票成功!当前票余量:25
    02窗口出票成功!当前票余量:24
    03窗口出票成功!当前票余量:23
    01窗口出票成功!当前票余量:22
    02窗口出票成功!当前票余量:21
    02窗口出票成功!当前票余量:20
    03窗口出票成功!当前票余量:19
    01窗口出票成功!当前票余量:18
    01窗口出票成功!当前票余量:17
    02窗口出票成功!当前票余量:16
    03窗口出票成功!当前票余量:15
    02窗口出票成功!当前票余量:14
    01窗口出票成功!当前票余量:13
    03窗口出票成功!当前票余量:12
    01窗口出票成功!当前票余量:11
    03窗口出票成功!当前票余量:10
    02窗口出票成功!当前票余量:9
    03窗口出票成功!当前票余量:8
    02窗口出票成功!当前票余量:7
    01窗口出票成功!当前票余量:6
    03窗口出票成功!当前票余量:5
    02窗口出票成功!当前票余量:4
    01窗口出票成功!当前票余量:3
    01窗口出票成功!当前票余量:2
    03窗口出票成功!当前票余量:1
    02窗口出票成功!当前票余量:0 今日票已卖完!
    01窗口:今日票已卖完!
    03窗口:今日票已卖完!
    
    Process finished with exit code 0
    
    
    更多相关内容
  • java多线程与高并发java多线程与高并发java多线程与高并发
  • java多线程执行任务(工具)

    千次阅读 2022-03-09 14:54:09
    在项目开发的过程中经常会碰到多线程执行任务,每次用线程池实现时,由于每次的需求都有所差别有时是所有任务同时执行有时是分批次执行有时还需要知道所有任务什么时候执行完。今天闲着写了一个通用的多线程执行工具...

    java多线程执行任务

    在项目开发的过程中经常会碰到多线程执行任务,每次用线程池实现时,由于每次的需求都有所差别有时是所有任务同时执行有时是分批次执行有时还需要知道所有任务什么时候执行完。今天闲着写了一个通用的多线程执行工具。

    java多线程执行任务(工具升级版)

    实现原理如图:

    在这里插入图片描述

    直接上代码

    Scheder

    
    import cn.hutool.core.util.RandomUtil;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.stream.IntStream;
    
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/7 15:29
     */
    @Slf4j
    public class Scheder {
    
        /**
         * 任务信息数组
         */
        private TaskInfo[] taskInfos;
    
        /**
         * 执行计划队列
         */
        private LinkedBlockingQueue<Plan> planQueue;
    
    
        /**
         * 允许并行执行的线程数
         */
        private ExecutorService loopExecutor;
    
        /**
         * 执行模式:
         * 1:所有任务信息都执行
         * 2:先执行部分任务,执行完后再执行其他任务
         */
        private int model;
    
        /**
         * 先执行的数量
         */
        private int modelSize;
    
        /**
         * model = 2 时有效
         */
        private ArrayList<Integer> indexList = new ArrayList<>();
    
        /**
         * 协助判断,是否线程池的任务全部结束
         */
        private AtomicInteger count;
    
        /**
         * 调度器的执行状态
         */
        private volatile boolean status = false;
    
    
        /** 构造器
         * @param taskInfos 任务数组
         * @param nThrends  同时执行任务中的计划线程数
         * @param queueSize 计划执行队列
         * @param model     执行模式 1:所有任务信息都执行 2:先执行部分任务,执行完后再执行其他任务
         * @param modelSize 每批执行任务的数量
         */
        public Scheder(TaskInfo[] taskInfos, int nThrends, int queueSize, int model, Integer modelSize) {
            this.taskInfos = taskInfos;
            this.planQueue = new LinkedBlockingQueue<>(queueSize);
            this.loopExecutor = Executors.newFixedThreadPool(nThrends);
            this.model = model;
            if (this.model == 2 && modelSize != null) {
                this.modelSize = modelSize > taskInfos.length ? taskInfos.length : modelSize;
            } else {
                this.modelSize = taskInfos.length;
            }
    		count = countPlan();
        }
    
        /**
         * 计算一共有多少执行计划
         * @return /
         */
        private AtomicInteger countPlan() {
            int sum = 0;
            for (int i = 0; i < this.taskInfos.length; i++) {
                sum += this.taskInfos[i].getPlanQueue().size();
            }
            return new AtomicInteger(sum);
        }
    
        public Scheder(TaskInfo[] taskInfos, int nThrends, int model, Integer modelSize) {
            this(taskInfos, nThrends, 100, model, modelSize);
        }
    
        public Scheder(TaskInfo[] taskInfos, int nThrends) {
            this(taskInfos, nThrends, 100, 1, null);
        }
    
        public Scheder(TaskInfo[] taskInfos) {
            this(taskInfos, 10, 100, 1, null);
        }
    
        public void setModel(int model) {
            this.model = model;
        }
    
        public int getModel() {
            return model;
        }
    
        public void setModelSize(int modelSize) {
            this.modelSize = modelSize;
        }
    
        public int getModelSize() {
            return modelSize;
        }
    
        public ArrayList<Integer> getIndexList() {
            return indexList;
        }
    
    
        public AtomicInteger getCount() {
            return count;
        }
    
        public boolean isStatus() {
            return status;
        }
    
        /**
         * 执行方法
         */
        public void run() {
            if (this.status) {
                log.warn("任务处于启动状态");
                return;
            }
            this.status = true;
            // 开启向队列中添加执行计划线程
            init();
            // 循环执行执行计划
            while (this.status) {
                // 所有执行计划执行完后,退出
                if (this.taskInfos.length <= 0 && this.planQueue.size() == 0) {
                    this.status = false;
                    break;
                }
                try {
                    // 获取一个执行计划
                    Plan plan = this.planQueue.take();
                    // 执行计划
                    this.loopExecutor.execute(() -> plan.run0(this.count));
                } catch (InterruptedException e) {
                    log.error("任务执行中发生异常", e);
                }
            }
            int size;
            // 所有线程执行完毕出循环
            for (; ; ) {
                size = this.count.get();
                if (size == 0) {
                    break;
                }
            }
    
            //停止线程池
            this.loopExecutor.shutdown();
            for (; ; ) {
                //只有当线程池中所有线程完成任务时才会返回true,并且需要先调用线程池的shutdown方法或者shutdownNow方法。
                if (this.loopExecutor.isTerminated()) {
                    System.out.println("执行结束!");
                    break;
                }
            }
    
        }
    
        /**
         * 开启一个线程,持续向执行计划队列添加执行计划,直到所有的计划任务添加完
         */
        private void init() {
            new Thread(() -> {
                while (this.status) {
                    // 任务信息数组数量
                    int length = this.taskInfos.length;
                    // 执行完结束线程
                    if (length <= 0) {
                        break;
                    }
                    // 获取添加执行计划的的任务索引值
                    int index = getIndexOfModel(this.model, length);
                    TaskInfo taskInfo = null;
                    try {
                        taskInfo = this.taskInfos[index];
    
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
                    LinkedList<Plan> plans = taskInfo.getPlanQueue();
                    if (plans.size() > 0) {
                        Plan plan = plans.removeFirst();
                        try {
                            this.planQueue.put(plan);
                        } catch (InterruptedException e) {
                            log.error("向执行计划队列放入计划异常", e);
                        }
                    } else {
                        this.taskInfos = reBuildTaskInfos(this.taskInfos, index);
                    }
                }
            }).start();
        }
    
        /**
         * 根据执行模式获取添加执行计划的的任务信息索引值
         *
         * @param model  执行模式
         * @param length 任务信息数组数量
         * @return 任务信息索引值
         */
        private int getIndexOfModel(int model, int length) {
            if (model == 1) {
                return RandomUtil.randomInt(0, length * 100) % length;
            } else {
            	this.indexList.removeIf(item -> item >= length);
                if (this.indexList.size() < this.modelSize) {
                    int index = RandomUtil.randomInt(0, length * 100) % length;
                    this.indexList.add(index);
                    return index;
                } else {
                    return this.indexList.get(RandomUtil.randomInt(0, length * 100) % this.indexList.size());
                }
            }
        }
    
        /**
         * 重新构建任务信息数组
         *
         * @param taskInfos 原来任务信息数组
         * @param index     需要移除的任务信息
         * @return 新的任务信息数组
         */
        private TaskInfo[] reBuildTaskInfos(TaskInfo[] taskInfos, int index) {
            TaskInfo[] newTaskINfo = new TaskInfo[taskInfos.length - 1];
            for (int j = 0, i = 0; i < taskInfos.length; i++) {
                if (i != index) {
                    newTaskINfo[j] = taskInfos[i];
                    j++;
                }
            }
            return newTaskINfo;
        }
    }
    
    

    构造器解释:

    • @param taskInfos 任务数组
    • @param nThrends 同时执行任务中的计划线程数
    • @param queueSize 计划执行队列
    • @param model 执行模式 1:所有任务信息都执行 2:先执行部分任务,执行完后再执行其他任务
    • @param modelSize 每批执行任务的数量

    TaskInfo

    
    import lombok.Data;
    
    import java.util.LinkedList;
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/7 15:31
     */
    @Data
    public class TaskInfo {
    
        /**
         * 任务名称
         */
        private String name;
    
        /**
         * 执行计划队列
         */
        private LinkedList<Plan> planQueue;
    
    
        public TaskInfo(String name, LinkedList<Plan> planQueue) {
            this.name = name;
            this.planQueue = planQueue;
        }
    }
    
    

    Plan

    
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/7 15:37
     */
    public interface Plan {
    
        /**
         * 线程池执行前
         */
        default void before(){
        }
    
    
        void run();
    
    
        /**
         * 线程池执行后
         */
        default void after(){
        }
    
        default void run0(AtomicInteger atomicInteger) {
            try{
                before();
                run();
            }finally {
                after();
                atomicInteger.decrementAndGet();
            }
        }
    
    
    }
    
    

    上面就是封装的工具

    实现自己的计划

    MyPlan

    
    
    import lombok.Data;
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/9 10:33
     */
    @Data
    public class MyPlan implements Plan {
        private String name;
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ":" + name);
        }
    }
    
    

    Test

    public class Test {
    	public static void main(String[] args) {
            int userSize = 1000;
            int jobSize = 100;
    
            TaskInfo[] taskInfos = new TaskInfo[userSize];
    
    
            IntStream.range(0, userSize).parallel().forEach(i -> {
                LinkedList<Plan> plans = new LinkedList<>();
                for (int j = 0; j < jobSize; j++) {
                    MyPlan myPlan = new MyPlan();
                    myPlan.setName("用户" + i + ",执行计划" + j);
                    plans.add(myPlan);
                }
                taskInfos[i] = new TaskInfo("用户" + i, plans);
            });
            Scheder scheder = new Scheder(taskInfos, 10, 2, 10);
            scheder.run();
        }
    }
    

    测试结果:

    在这里插入图片描述

    展开全文
  • java多线程执行任务(工具升级版)

    千次阅读 2022-03-10 16:17:59
    昨天写的java多线程执行任务(工具)但是不能符合顺序执行计划的场景,下面升级一下原工具 [java多线程执行任务(工具)]: https://haohaoding.blog.csdn.net/article/details/123377795

    java多线程执行任务(工具升级版)

    昨天写的java多线程执行任务(工具)但是不能符合顺序执行计划的场景,下面升级一下原工具

    java多线程执行任务(工具)

    更新java多线程执行任务(工具再升级版)

    之前只支持两种模式,新工具支持四种模式

    执行模式:

    • 1:所有任务信息都执行
    • 2:先执行部分任务,执行完后再执行其他任务
    • 3:所有任务信息都执行,但是顺序执行每个任务中的计划
    • 4:顺序先执行执行任务中的计划,执行完后再顺序执行其他任务

    模式3,4在模式1,2上顺序执行每个任务中的计划

    实现原理如图:
    在这里插入图片描述

    接着上代码

    Scheder

    
    
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.util.RandomUtil;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.stream.IntStream;
    
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/7 15:29
     */
    @Slf4j
    public class Scheder {
    
        /**
         * 任务信息数组
         */
        private TaskInfo[] taskInfos;
    
    
        /**
         * 执行计划队列数组
         */
        private LinkedBlockingQueue<Plan>[] planQueueArray;
    
        /**
         * 队列的数量
         */
        int queueNum;
    
        /**
         * 队列的容量
         */
        int queueSize;
    
        /**
         * 允许并行执行的线程池
         */
        private ExecutorService loopExecutor;
    
        /**
         * 允许并行执行的线程数
         */
        private int nThrends;
    
        /**
         * 执行模式:
         * 1:所有任务信息都执行
         * 2:先执行部分任务,执行完后再执行其他任务
         * 3:顺序执行任务中的计划
         * 4:顺序先执行执行任务中的计划,执行完后再顺序执行其他任务
         */
        private int model;
    
        /**
         * 每批执行的任务数量
         */
        private int modelSize;
    
        /**
         * model = 2,4 时有效
         */
        private ArrayList<Integer> indexList = new ArrayList<>();
    
    
        /**
         * 协助判断,是否线程池的任务全部结束
         */
        private AtomicInteger count;
    
        /**
         * 调度器的执行状态
         */
        private volatile boolean status = false;
    
    
        /**
         * 构造器
         *
         * @param taskInfos 任务数组
         * @param nThrends  同时执行任务中的计划线程数
         * @param queueSize 计划执行队列
         * @param model     执行模式 1:所有任务信息都执行 2:先执行部分任务,执行完后再执行其他任务
         * @param modelSize 每批执行任务的数量
         */
        public Scheder(TaskInfo[] taskInfos, int nThrends, int queueNum, int queueSize, int model, Integer modelSize) {
            this.taskInfos = taskInfos;
            this.nThrends = nThrends;
            this.queueNum = queueNum;
            this.queueSize = queueSize;
            this.loopExecutor = Executors.newFixedThreadPool(this.nThrends);
            this.model = model;
    
            if (this.model < 3) {
                this.planQueueArray = new LinkedBlockingQueue[1];
                this.planQueueArray[0] = new LinkedBlockingQueue<>(this.queueSize);
            } else {
                // 初始化队列数组
                this.planQueueArray = new LinkedBlockingQueue[this.queueNum];
                IntStream.range(0, this.queueNum).forEach(i -> this.planQueueArray[i] = new LinkedBlockingQueue<>(this.queueSize));
            }
    
            // modelSize只有在等于2,4有效
            if (this.model == 2 || this.model == 4) {
                this.modelSize = modelSize > taskInfos.length ? taskInfos.length : modelSize;
            }
    
    
            count = countPlan();
        }
    
        /**
         * 计算一共有多少执行计划
         *
         * @return /
         */
        private AtomicInteger countPlan() {
            int sum = 0;
            for (int i = 0; i < this.taskInfos.length; i++) {
                sum += this.taskInfos[i].getPlanQueue().size();
            }
            return new AtomicInteger(sum);
        }
    
        public Scheder(TaskInfo[] taskInfos, int nThrends, int model, Integer modelSize) {
            this(taskInfos, nThrends, nThrends, 100, model, modelSize);
        }
    
        public Scheder(TaskInfo[] taskInfos, int nThrends) {
            this(taskInfos, nThrends, nThrends, 100, 1, null);
        }
    
        public Scheder(TaskInfo[] taskInfos) {
            this(taskInfos, 10, 10, 100, 1, null);
        }
    
        public void setModel(int model) {
            this.model = model;
        }
    
        public int getModel() {
            return model;
        }
    
        public void setModelSize(int modelSize) {
            this.modelSize = modelSize;
        }
    
        public int getModelSize() {
            return modelSize;
        }
    
        public ArrayList<Integer> getIndexList() {
            return indexList;
        }
    
    
        public AtomicInteger getCount() {
            return count;
        }
    
        public boolean isStatus() {
            return status;
        }
    
        /**
         * 执行方法
         */
        public void run() {
            if (this.status) {
                log.warn("任务处于启动状态");
                return;
            }
            this.status = true;
            // 开启向队列中添加执行计划线程
            init();
            // 循环执行执行计划
            while (this.status) {
                // 所有执行计划执行完后,退出
                if (this.taskInfos.length <= 0) {
                    if (this.model < 3) {
                        if (this.planQueueArray[0].size() == 0) {
                            this.status = false;
                            break;
                        }
                    } else {
                        ArrayList<Integer> notEmptyIndex = getNotEmptyIndex(this.planQueueArray);
                        if (CollUtil.isEmpty(notEmptyIndex)) {
                            this.status = false;
                            break;
                        }
                    }
                }
    
                // 执行计划
                execute();
    
            }
            int size;
            // 所有线程执行完毕出循环
            for (; ; ) {
                size = this.count.get();
                if (size == 0) {
                    break;
                }
            }
    
            //停止线程池
            this.loopExecutor.shutdownNow();
            for (; ; ) {
                //只有当线程池中所有线程完成任务时才会返回true,并且需要先调用线程池的shutdown方法或者shutdownNow方法。
                if (this.loopExecutor.isTerminated()) {
                    System.out.println("执行结束!");
                    break;
                }
            }
    
        }
    
        private void execute() {
            if (this.model < 3) {
                try {
                    // 获取一个执行计划
                    Plan plan = this.planQueueArray[0].take();
                    // 执行计划
                    this.loopExecutor.execute(() -> plan.run0(this.count));
                } catch (InterruptedException e) {
                    log.error("任务执行中发生异常", e);
                }
            } else {
                this.loopExecutor.execute(() -> {
                    try {
                        // 获取一个执行计划
                        Plan plan = null;
                        // 获取线程id
                        String name = Thread.currentThread().getName();
                        int lastIndexOf = name.lastIndexOf("-");
                        int id = Integer.parseInt(name.substring(lastIndexOf + 1));
                        ArrayList<Integer> notEmptyIndex2 = getNotEmptyIndex(this.planQueueArray);
                        Integer index = notEmptyIndex2.stream().filter(item -> item % this.nThrends == (id - 1)).findAny().orElse(null);
                        if (index == null) {
                            return;
                        }
                        LinkedBlockingQueue<Plan> plans = this.planQueueArray[index];
                        if (plans.size() > 0) {
                            plan = plans.take();
                            plan.run0(this.count);
                        }
                    } catch (InterruptedException e) {
                        log.error("任务执行中发生异常", e);
                    }
                });
            }
        }
    
        private ArrayList<Integer> getNotEmptyIndex(LinkedBlockingQueue<Plan>[] planQueueArray) {
            ArrayList<Integer> indexArray = new ArrayList<>();
            for (int i = 0; i < planQueueArray.length; i++) {
                if (!CollUtil.isEmpty(planQueueArray[i])) {
                    indexArray.add(i);
                }
            }
            return indexArray;
        }
    
        /**
         * 开启一个线程,持续向执行计划队列添加执行计划,直到所有的计划任务添加完
         */
        private void init() {
            new Thread(() -> {
                while (this.status) {
                    // 任务信息数组数量
                    int length = this.taskInfos.length;
                    // 执行完结束线程
                    if (length <= 0) {
                        break;
                    }
                    // 获取添加执行计划的的任务索引值
                    int index = getIndexOfModel(this.model, length);
                    TaskInfo taskInfo = null;
                    try {
                        taskInfo = this.taskInfos[index];
    
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
                    LinkedList<Plan> plans = taskInfo.getPlanQueue();
                    if (plans.size() > 0) {
                        try {
                            if (this.model >= 3) {
                                int index2 = taskInfo.getId() % this.planQueueArray.length;
                                this.planQueueArray[index2].put(plans.removeFirst());
                            } else {
                                this.planQueueArray[0].put(plans.removeFirst());
                            }
                        } catch (InterruptedException e) {
                            log.error("向执行计划队列放入计划异常", e);
                        }
                    } else {
                        this.taskInfos = reBuildTaskInfos(this.taskInfos, index);
                    }
                }
            }).start();
        }
    
        /**
         * 根据执行模式获取添加执行计划的的任务信息索引值
         *
         * @param model  执行模式
         * @param length 任务信息数组数量
         * @return 任务信息索引值
         */
        private int getIndexOfModel(int model, int length) {
            if (model == 1 || model == 3) {
                return RandomUtil.randomInt(0, length) % length;
            } else {
                this.indexList.removeIf(item -> item >= length);
                if (this.indexList.size() < this.modelSize) {
                    int index = RandomUtil.randomInt(0, length) % length;
                    this.indexList.add(index);
                    return index;
                } else {
                    return this.indexList.get(RandomUtil.randomInt(0, length) % this.indexList.size());
                }
            }
        }
    
        /**
         * 重新构建任务信息数组
         *
         * @param taskInfos 原来任务信息数组
         * @param index     需要移除的任务信息
         * @return 新的任务信息数组
         */
        private TaskInfo[] reBuildTaskInfos(TaskInfo[] taskInfos, int index) {
            TaskInfo[] newTaskINfo = new TaskInfo[taskInfos.length - 1];
            for (int j = 0, i = 0; i < taskInfos.length; i++) {
                if (i != index) {
                    newTaskINfo[j] = taskInfos[i];
                    j++;
                }
            }
            return newTaskINfo;
        }
    
    }
    

    TaskInfo

    
    
    import lombok.Data;
    
    import java.util.LinkedList;
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/7 15:31
     */
    @Data
    public class TaskInfo {
    
        /**
         * 唯一标识
         */
        private int id;
    
        /**
         * 任务名称
         */
        private String name;
    
        /**
         * 执行计划队列
         */
        private LinkedList<Plan> planQueue;
    
    
        public TaskInfo(int id, String name, LinkedList<Plan> planQueue) {
            this.id = id;
            this.name = name;
            this.planQueue = planQueue;
        }
    }
    
    

    这里加了id属性

    Plan

    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/7 15:37
     */
    public interface Plan {
    
        /**
         * 线程池执行前
         */
        default void before(){
        }
    
    
        void run();
    
    
        /**
         * 线程池执行后
         */
        default void after(){
        }
    
        default void run0(AtomicInteger atomicInteger) {
            try{
                before();
                run();
            }finally {
                after();
                atomicInteger.decrementAndGet();
            }
        }
    
    
    }
    
    

    修改完成

    实现自己的计划

    MyPlan

    
    
    import lombok.Data;
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/9 10:33
     */
    @Data
    public class MyPlan implements Plan {
        private String name;
    
        @Override
        public void run() {
    //        if( name.startsWith("用户99")){
                System.out.println(Thread.currentThread().getName() + ":" + name);
    //        }
        }
    }
    

    Test

    
    import java.util.LinkedList;
    import java.util.stream.IntStream;
    
    /**
     * @Author: dinghao
     * @Date: 2022/3/9 14:52
     */
    public class Test {
        public static void main(String[] args) {
            int userSize = 100;
            int jobSize = 1000;
    
            TaskInfo[] taskInfos = new TaskInfo[userSize];
    
    
            IntStream.range(0, userSize).parallel().forEach(i -> {
                LinkedList<Plan> plans = new LinkedList<>();
                for (int j = 0; j < jobSize; j++) {
                    MyPlan myPlan = new MyPlan();
                    myPlan.setName("用户" + i + ",执行计划" + j);
                    plans.add(myPlan);
                }
                taskInfos[i] = new TaskInfo(i, "用户" + i, plans);
            });
    
    
            Scheder scheder = new Scheder(taskInfos, 3, 10, 100, 3, 2);
            scheder.run();
        }
    }
    

    测试结果:
    在这里插入图片描述

    展开全文
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...

    引言

    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。
    那么话不多说,今天本帅将记录自己线程的学习。

    程序,进程,线程的基本概念+并行与并发:

    程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
    进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
    线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

    即:线程《线程(一个程序可以有多个线程)
    程序:静态的代码 进程:动态执行的程序
    线程:进程中要同时干几件事时,每一件事的执行路径成为线程。

    并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
    并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

    线程的相关API

    //获取当前线程的名字
    Thread.currentThread().getName()

    1.start():1.启动当前线程2.调用线程中的run方法
    2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    3.currentThread():静态方法,返回执行当前代码的线程
    4.getName():获取当前线程的名字
    5.setName():设置当前线程的名字
    6.yield():主动释放当前线程的执行权
    7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
    8.stop():过时方法。当执行此方法时,强制结束当前线程。
    9.sleep(long millitime):线程休眠一段时间
    10.isAlive():判断当前线程是否存活

    判断是否是多线程

    一条线程即为一条执行路径,即当能用一条路径画出来时即为一个线程
    例:如下看似既执行了方法一,又执行了方法2,但是其实质就是主线程在执行方法2和方法1这一条路径,所以就是一个线程

    public class Sample{
    		public void method1(String str){
    			System.out.println(str);
    		}
    	
    	public void method2(String str){
    		method1(str);
    	}
    	
    	public static void main(String[] args){
    		Sample s = new Sample();
    		s.method2("hello");
    	}
    }
    

    在这里插入图片描述

    线程的调度

    调度策略:
    时间片:线程的调度采用时间片轮转的方式
    抢占式:高优先级的线程抢占CPU
    Java的调度方法:
    1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
    2.对高优先级,使用优先调度的抢占式策略

    线程的优先级

    等级:
    MAX_PRIORITY:10
    MIN_PRIORITY:1
    NORM_PRIORITY:5

    方法:
    getPriority():返回线程优先级
    setPriority(int newPriority):改变线程的优先级

    注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

    多线程的创建方式

    1. 方式1:继承于Thread类

    1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
    2.重写Thread类的run()方法
    3.创建Thread子类的对象
    4.通过此对象调用start()方法

    start与run方法的区别:

    start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
    调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
    run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

    总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)
    在这里插入图片描述

    多线程例子(火车站多窗口卖票问题)

    	package com.example.paoduantui.Thread;
    	
    	import android.view.Window;
    	
    	/**
    	 *
    	 * 创建三个窗口卖票,总票数为100张,使用继承自Thread方式
    	 * 用静态变量保证三个线程的数据独一份
    	 * 
    	 * 存在线程的安全问题,有待解决
    	 *
    	 * */
    	
    	public class ThreadDemo extends Thread{
    	
    	    public static void main(String[] args){
    	        window t1 = new window();
    	        window t2 = new window();
    	        window t3 = new window();
    	
    	        t1.setName("售票口1");
    	        t2.setName("售票口2");
    	        t3.setName("售票口3");
    	
    	        t1.start();
    	        t2.start();
    	        t3.start();
    	    }
    	
    	}
    	
    	class window extends Thread{
    	    private static int ticket = 100; //将其加载在类的静态区,所有线程共享该静态变量
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    2. 方式2:实现Runable接口方式

    1.创建一个实现了Runable接口的类
    2.实现类去实现Runnable中的抽象方法:run()
    3.创建实现类的对象
    4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
    5.通过Thread类的对象调用start()

    具体操作,将一个类实现Runable接口,(插上接口一端)。
    另外一端,通过实现类的对象与线程对象通过此Runable接口插上接口实现

    	package com.example.paoduantui.Thread;
    	
    	public class ThreadDemo01 {
    	    
    	    public static  void main(String[] args){
    	        window1 w = new window1();
    	        
    	        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
    	        
    	        Thread t1=new Thread(w);
    	        Thread t2=new Thread(w);
    	        Thread t3=new Thread(w);
    	
    	        t1.setName("窗口1");
    	        t2.setName("窗口2");
    	        t3.setName("窗口3");
    	        
    	        t1.start();
    	        t2.start();
    	        t3.start();
    	    }
    	}
    	
    	class window1 implements Runnable{
    	    
    	    private int ticket = 100;
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    比较创建线程的两种方式:
    开发中,优先选择实现Runable接口的方式
    原因1:实现的方式没有类的单继承性的局限性
    2:实现的方式更适合用来处理多个线程有共享数据的情况
    联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

    3.新增的两种创建多线程方式

    1.实现callable接口方式:

    与使用runnable方式相比,callable功能更强大些:
    runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
    方法可以抛出异常
    支持泛型的返回值
    需要借助FutureTask类,比如获取返回结果

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * 创建线程的方式三:实现callable接口。---JDK 5.0新增
     *是否多线程?否,就一个线程
     *
     * 比runable多一个FutureTask类,用来接收call方法的返回值。
     * 适用于需要从线程中接收返回值的形式
     * 
     * //callable实现新建线程的步骤:
     * 1.创建一个实现callable的实现类
     * 2.实现call方法,将此线程需要执行的操作声明在call()中
     * 3.创建callable实现类的对象
     * 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
     * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)
     * 
     * */
    
    
    //实现callable接口的call方法
    class NumThread implements Callable{
    
        private int sum=0;//
    
        //可以抛出异常
        @Override
        public Object call() throws Exception {
            for(int i = 0;i<=100;i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    sum += i;
                }
            }
            return sum;
        }
    }
    
    public class ThreadNew {
    
        public static void main(String[] args){
            //new一个实现callable接口的对象
            NumThread numThread = new NumThread();
    
            //通过futureTask对象的get方法来接收futureTask的值
            FutureTask futureTask = new FutureTask(numThread);
    
            Thread t1 = new Thread(futureTask);
            t1.setName("线程1");
            t1.start();
    
            try {
                //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
               Object sum = futureTask.get();
               System.out.println(Thread.currentThread().getName()+":"+sum);
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    使用线程池的方式:

    背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
    思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)
    好处:提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止
    。。。。。。

    JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors
    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
    void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
    Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
    void shutdown():关闭连接池。

    Executors工具类,线程池的工厂类,用于创建并返回不同类型的线程池
    Executors.newCachedThreadPool()创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool(n)创建一个可重用固定线程数的线程池
    Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool(n)创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    线程池构造批量线程代码如下:

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 创建线程的方式四:使用线程池(批量使用线程)
     *1.需要创建实现runnable或者callable接口方式的对象
     * 2.创建executorservice线程池
     * 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
     * 4.关闭线程池
     *
     * */
    
    class NumberThread implements Runnable{
    
    
        @Override
        public void run() {
            for(int i = 0;i<=100;i++){
                if (i % 2 ==0 )
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    
    class NumberThread1 implements Runnable{
        @Override
        public void run() {
            for(int i = 0;i<100; i++){
                if(i%2==1){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
    }
    
    public class ThreadPool {
    
        public static void main(String[] args){
    
            //创建固定线程个数为十个的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
    
            //new一个Runnable接口的对象
            NumberThread number = new NumberThread();
            NumberThread1 number1 = new NumberThread1();
    
            //执行线程,最多十个
            executorService.execute(number1);
            executorService.execute(number);//适合适用于Runnable
    
            //executorService.submit();//适合使用于Callable
            //关闭线程池
            executorService.shutdown();
        }
    
    }
    

    目前两种方式要想调用新线程,都需要用到Thread中的start方法。

    java virtual machine(JVM):java虚拟机内存结构

    程序(一段静态的代码)——————》加载到内存中——————》进程(加载到内存中的代码,动态的程序)
    进程可细分为多个线程,一个线程代表一个程序内部的一条执行路径
    每个线程有其独立的程序计数器(PC,指导着程序向下执行)与运行栈(本地变量等,本地方法等)
    在这里插入图片描述

    大佬传送门:https://blog.csdn.net/bluetjs/article/details/52874852

    线程通信方法:

    wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

    由于wait,notify,以及notifyAll都涉及到与锁相关的操作
    wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
    notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

    有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。

    所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

    线程的分类:

    java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)

    若JVM中都是守护线程,当前JVM将退出。(形象理解,唇亡齿寒)

    线程的生命周期:

    JDK中用Thread.State类定义了线程的几种状态,如下:

    线程生命周期的阶段描述
    新建当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
    就绪处于新建状态的线程被start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
    运行当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能
    阻塞在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
    死亡线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

    在这里插入图片描述

    线程的同步:在同步代码块中,只能存在一个线程。

    线程的安全问题:

    什么是线程安全问题呢?
    线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

    上述例子中:创建三个窗口卖票,总票数为100张票
    1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
    2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
    生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
    3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
    4.在java中,我们通过同步机制,来解决线程的安全问题。

    方式一:同步代码块
    使用同步监视器(锁)
    Synchronized(同步监视器){
    //需要被同步的代码
    }
    说明:

    1. 操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
    2. 共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
    3. 同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)

    Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

    方式二:同步方法
    使用同步方法,对方法进行synchronized关键字修饰
    将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
    对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
    而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

    总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
    2.非静态的同步方法,同步监视器是this
    静态的同步方法,同步监视器是当前类本身。继承自Thread。class

    方式三:JDK5.0新增的lock锁方法

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.locks.ReentrantLock;
    
    class Window implements Runnable{
        private int ticket = 100;//定义一百张票
        //1.实例化锁
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            
                while (true) {
    
                    //2.调用锁定方法lock
                    lock.lock();
    
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票");
                        ticket--;
                    } else {
                        break;
                    }
                }
    
    
            }
    }
    
    public class LockTest {
    
        public static void main(String[] args){
           Window w= new Window();
    
           Thread t1 = new Thread(w);
           Thread t2 = new Thread(w);
           Thread t3 = new Thread(w);
    
           t1.setName("窗口1");
           t2.setName("窗口1");
           t3.setName("窗口1");
    
           t1.start();
           t2.start();
           t3.start();
        }
    
    }
    

    总结:Synchronized与lock的异同?

    相同:二者都可以解决线程安全问题
    不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
    lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)

    优先使用顺序:
    LOCK-》同步代码块-》同步方法

    判断线程是否有安全问题,以及如何解决:

    1.先判断是否多线程
    2.再判断是否有共享数据
    3.是否并发的对共享数据进行操作
    4.选择上述三种方法解决线程安全问题

    例题:

    	package com.example.paoduantui.Thread;
    	
    	/***
    	 * 描述:甲乙同时往银行存钱,存够3000
    	 *
    	 *
    	 * */
    	
    	//账户
    	class Account{
    	    private double balance;//余额
    	    //构造器
    	    public Account(double balance) {
    	        this.balance = balance;
    	    }
    	    //存钱方法
    	    public synchronized void deposit(double amt){
    	        if(amt>0){
    	            balance +=amt;
    	            try {
    	                Thread.sleep(1000);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	            System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
    	        }
    	    }
    	}
    	
    	//两个顾客线程
    	class Customer extends Thread{
    	     private Account acct;
    	
    	     public Customer(Account acct){
    	         this.acct = acct;
    	     }
    	
    	
    	
    	    @Override
    	    public void run() {
    	        for (int i = 0;i<3;i++){
    	            acct.deposit(1000);
    	        }
    	    }
    	}
    	
    	//主方法,之中new同一个账户,甲乙两个存钱线程。
    	public class AccountTest {
    	
    	    public static void main(String[] args){
    	        Account acct = new Account(0);
    	        Customer c1 = new Customer(acct);
    	        Customer c2 = new Customer(acct);
    	
    	        c1.setName("甲");
    	        c2.setName("乙");
    	
    	        c1.start();
    	        c2.start();
    	    }
    	
    	}
    

    解决单例模式的懒汉式的线程安全问题:

    单例:只能通过静态方法获取一个实例,不能通过构造器来构造实例
    1.构造器的私有化:
    private Bank(){}//可以在构造器中初始化东西
    private static Bank instance = null;//初始化静态实例

    public static Bank getInstance(){
    if(instance!=null){
    instance = new Bank();
    }
    return instance;
    }

    假设有多个线程调用此单例,而调用的获取单例的函数作为操作共享单例的代码块并没有解决线程的安全问题,会导致多个线程都判断实例是否为空,此时就会导致多个实例的产生,也就是单例模式的线程安全问题。

    解决线程安全问题的思路:

    1. 将获取单例的方法改写成同部方法,即加上synchronized关键字,此时同步监视器为当前类本身。(当有多个线程并发的获取实例时,同时只能有一个线程获取实例),解决了单例模式的线程安全问题。
    2. 用同步监视器包裹住同步代码块的方式。

    懒汉式单例模式的模型,例如:生活中的限量版的抢购:
    当一群人并发的抢一个限量版的东西的时候,可能同时抢到了几个人,他们同时进入了房间(同步代码块内)
    但是只有第一个拿到限量版东西的人才能到手,其余人都不能拿到,所以效率稍高的做法是,当东西被拿走时,我们在门外立一块牌子,售罄。
    这样就减少了线程的等待。即下面效率稍高的懒汉式写法:

    package com.example.paoduantui.Thread;
    
    public class Bank {
        //私有化构造器
        private Bank(){}
        //初始化静态实例化对象
        private static  Bank instance = null;
    
        //获取单例实例,此种懒汉式单例模式存在线程不安全问题(从并发考虑)
    
        public static  Bank getInstance(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
    
        //同步方法模式的线程安全
        public static synchronized Bank getInstance1(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
        //同步代码块模式的线程安全(上锁)
        public  static Bank getInstance2(){
            synchronized (Bank.class){
                if(instance==null){
                    instance = new Bank();
                }
                return  instance;
            }
        }
        
        //效率更高的线程安全的懒汉式单例模式
        /**
         * 由于当高并发调用单例模式的时候,类似于万人夺宝,只有第一个进入房间的人才能拿到宝物,
         * 当多个人进入这个房间时,第一个人拿走了宝物,也就另外几个人需要在同步代码块外等候,
         * 剩下的人只需要看到门口售罄的牌子即已知宝物已经被夺,可以不用进入同步代码块内,提高了效率。
         * 
         * 
         * */
        public static Bank getInstance3(){
            if (instance==null){
                synchronized (Bank.class){
                    if(instance==null){
                        instance = new Bank();
                    }
                }
            }
            return  instance;
        }
    }
    

    线程的死锁问题:

    线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
    出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续

    package com.example.paoduantui.Thread;
    
    
    /**
     * 演示线程的死锁问题
     *
     * */
    public class Demo {
    
        public static void main(String[] args){
    
            final StringBuffer s1 = new StringBuffer();
            final StringBuffer s2 = new StringBuffer();
    
    
            new Thread(){
                @Override
                public void run() {
                    //先拿锁一,再拿锁二
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
    
                        synchronized (s2) {
                            s1.append("b");
                            s2.append("2");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }.start();
    
            //使用匿名内部类实现runnable接口的方式实现线程的创建
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
    
                        synchronized (s1) {
                            s1.append("d");
                            s2.append("4");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    
    }
    

    运行结果:
    1.先调用上面的线程,再调用下面的线程:
    在这里插入图片描述
    2.出现死锁:
    在这里插入图片描述
    3.先调用下面的线程,再调用上面的线程。
    在这里插入图片描述

    死锁的解决办法:

    1.减少同步共享变量
    2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
    3.减少锁的嵌套。

    线程的通信

    通信常用方法:

    通信方法描述
    wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    notify一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
    notifyAll一旦执行此方法,就会唤醒所有被wait()的线程

    使用前提:这三个方法均只能使用在同步代码块或者同步方法中。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的例子:使用两个线程打印1—100,线程1,线程2交替打印
     *
     * 当我们不采取线程之间的通信时,无法达到线程1,2交替打印(cpu的控制权,是自动分配的)
     * 若想达到线程1,2交替打印,需要:
     * 1.当线程1获取锁以后,进入代码块里将number++(数字打印并增加)操作完以后,为了保证下个锁为线程2所有,需要将线程1阻塞(线程1你等等wait())。(输出1,number为2)
     * 2.当线程2获取锁以后,此时线程1已经不能进入同步代码块中了,所以,为了让线程1继续抢占下一把锁,需要让线程1的阻塞状态取消(通知线程1不用等了notify()及notifyAll()),即应该在进入同步代码块时取消线程1的阻塞。
     *
     * */
    
    class Number implements Runnable{
    
        private int number = 1;//设置共享数据(线程之间对于共享数据的共享即为通信)
    
    
        //对共享数据进行操作的代码块,需要线程安全
        @Override
        public synchronized void run() {
    
            while(true){
                //使得线程交替等待以及通知交替解等待
                notify();//省略了this.notify()关键字
                if(number<100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
    
    public class CommunicationTest {
    
        public static void main(String[] args){
            //创建runnable对象
            Number number = new Number();
    
            //创建线程,并实现runnable接口
            Thread t1 = new Thread(number);
            Thread t2 = new Thread(number);
    
            //给线程设置名字
            t1.setName("线程1");
            t2.setName("线程2");
    
            //开启线程
            t1.start();
            t2.start();
    
        }
    
    }
    

    sleep和wait的异同:

    相同点:一旦执行方法以后,都会使得当前的进程进入阻塞状态
    不同点:
    1.两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
    2.调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
    3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放

    经典例题:生产者/消费者问题:

    生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

    这里可能出现两个问题:
    生产者比消费者快的时候,消费者会漏掉一些数据没有收到。
    消费者比生产者快时,消费者会去相同的数据。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的应用:生产者/消费者问题
     *
     * 1.是否是多线程问题?是的,有生产者线程和消费者线程(多线程的创建,四种方式)
     * 2.多线程问题是否存在共享数据? 存在共享数据----产品(同步方法,同步代码块,lock锁)
     * 3.多线程是否存在线程安全问题? 存在----都对共享数据产品进行了操作。(三种方法)
     * 4.是否存在线程间的通信,是,如果生产多了到20时,需要通知停止生产(wait)。(线程之间的通信问题,需要wait,notify等)
     *
     * */
    
    
    	class Clerk{
    	
    	    private int productCount = 0;
    	
    	
    	    //生产产品
    	    public synchronized void produceProduct() {
    	
    	        if(productCount<20) {
    	            productCount++;
    	
    	            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
    	            notify();
    	        }else{
    	            //当有20个时,等待wait
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	
    	    //消费产品
    	    public synchronized void consumeProduct() {
    	        if (productCount>0){
    	            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
    	            productCount--;
    	            notify();
    	        }else{
    	            //当0个时等待
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	}
    	
    	class Producer extends Thread{//生产者线程
    	
    	    private Clerk clerk;
    	
    	    public Producer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        try {
    	            sleep(10);
    	        } catch (InterruptedException e) {
    	            e.printStackTrace();
    	        }
    	        System.out.println(Thread.currentThread().getName()+";开始生产产品......");
    	
    	        while(true){
    	            clerk.produceProduct();
    	        }
    	    }
    	}
    	
    	class Consumer implements Runnable{//消费者线程
    	
    	    private Clerk clerk;
    	
    	    public Consumer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        System.out.println(Thread.currentThread().getName()+":开始消费产品");
    	
    	        while(true){
    	            try {
    	                Thread.sleep(1);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	
    	            clerk.consumeProduct();
    	        }
    	
    	    }
    	}
    	
    	public class ProductTest {
    	
    	    public static void main(String[] args){
    	        Clerk clerk = new Clerk();
    	
    	        Producer p1 = new Producer(clerk);
    	        p1.setName("生产者1");
    	
    	        Consumer c1 = new Consumer(clerk);
    	        Thread t1 = new Thread(c1);
    	        t1.setName("消费者1");
    	
    	        p1.start();
    	        t1.start();
    	
    	    }
    	
    	}
    
    展开全文
  • Java多线程 - 锁

    千次阅读 2022-03-28 15:31:49
    Java多线程 - 锁 三性 可见性 指的是线程之间的可见性,一个线程对状态的修改,对其他线程是可见的。在 Java中 volatile、synchronized 和 final 实现可见性。 原子性 如果一个操作是不可分割的,我们则称之为...
  • Java多线程【三种实现方法】

    千次阅读 2022-03-23 14:10:13
    java多线程 并发与并行 并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行 并行:一组程序...
  • Java 多线程编程基础(详细)

    万次阅读 多人点赞 2020-11-03 17:36:30
    Java多线程编程基础进程与线程多线程实现Thread类实现多线程Runnable接口实现多线程Callable接口实现多线程多线程运行状态多线程常用操作方法线程的命名和获取线程休眠线程中断线程强制执行线程让步线程优先级设定...
  • java 多线程 面试题整理(更新......)

    万次阅读 多人点赞 2021-11-30 16:23:06
    3、什么是同步执行和异步执行4、Java中实现多线程有几种方法?(较难)(1)继承Thread类(2)实现runable接口(3)实现Callable接口(创建FutureTask(Callable)对象)5、Future接口,Callable接口,FutureTask实现类的...
  • Java多线程实现多用户与服务端Socket通信

    千次阅读 多人点赞 2020-11-02 13:54:36
    Java多线程实现多用户与服务端Socket通信,类似QQ、微信、视频等客户端,多用户与服务器通信。详细记录服务端多线程的实现,目标是多用户(客户端)能够同时与服务器建立连接并通信,避免阻塞,进一步完善TCP的...
  • Java多线程学习(吐血超详细总结)

    万次阅读 多人点赞 2015-03-14 13:13:17
    本文主要讲了java多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  • Java多线程】停止线程

    万次阅读 2021-10-03 22:37:21
    Java中有以下3种方法可以终止正在进行的线程: 1)使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 2)使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resum
  • java多线程查询

    千次阅读 2021-03-05 17:06:15
    标签:由于最近工作遇到性能问题,尝试研究用多线程来实现,结果速度快了好几倍下面是多线程查询的部分代码,提供给大家参考下:线程类:带返回值的类要实现Callable接口,具体业务逻辑没有实现,只是写了个空方法在...
  • java多线程并发编程例子

    热门讨论 2012-03-08 12:11:38
    关于java.util.concurrent多线程核心包内各种线程资源的使用场景例子
  • Java多线程进阶

    千次阅读 2021-03-01 06:23:10
    首先是发生在多线程的情况下;线程a,线程b,共享资源share例如,share的资源一次只能被一个对象操作,这时候需要一个东西来标识(也叫监视器)出来表明该资源已经有人(指的是线程)在使用了,请还要使用的人(指的是线程)进入...
  • Java多线程变量共享与隔离

    千次阅读 2021-11-12 11:38:42
    线程相关 线程的相关API Thread.currentThread().getName():获取当前线程的名字 start():1.启动当前线程2.调用线程中的run方法 run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 ...
  • 真的还有必要学习JAVA多线程吗?

    千次阅读 多人点赞 2022-02-18 21:02:28
    JAVA多线程是不是真的没必要再学了?
  • java 多线程面试题及答案

    千次阅读 2021-09-18 09:20:33
    并发操作系统会根据任务调度系统给线程分配线程的 CPU 执行时间,线程的执行会进行切换。 线程和进程的区别? 1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)一个程序至少有一个...
  • Java多线程(超详细!)

    万次阅读 多人点赞 2021-05-12 17:00:59
    注意:一个进程可以启动线程。 eg.对于java程序来说,当在DOS命令窗口中输入: java HelloWorld 回车之后。 会先启动JVM,而JVM就是一个进程。 JVM再启动一个主线程调用main方法。 同时再启动一个垃圾回收线程...
  • Java 多线程:彻底搞懂线程池

    万次阅读 多人点赞 2019-07-09 19:27:00
    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。
  • 若一个进程中同一时间并行执行多个线程,就是支持多线程的。线程作为调度和执行的单位,每个线程都拥有独立的运行栈和程序计数器,一个进程中的多个线程共享相同的内存单元/内存地址空间——>他们从
  • Java多线程面试题(面试必备)

    万次阅读 多人点赞 2020-05-26 01:15:38
    文章目录一、多线程基础基础知识1. 并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程...
  • JAVA多线程是什么

    千次阅读 2020-08-31 23:53:23
    一、 什么是多线程: 我们现在所使用操作系统都是多任务操作系统(早期使用的DOS操作系统为单任务操作系统),多任务操作指在同一时刻可以同时做多件事(可以同时执行多个程序)。 多进程:每个程序都是一个进程,在操作...
  • 并发操作之——java多线程常用的锁

    千次阅读 2021-09-07 19:29:21
    并发操作之——java多线程常用的锁 并发操作之——java多线程常用的锁并发操作前言一、共享锁二、互斥锁三、死锁1、偏向锁2、轻量锁3、重量级锁总结 前言 并发操作之——java多线程常用的锁。 一、共享锁 也叫S...
  • java 多线程并发查询mysql数据

    千次阅读 2020-09-29 17:24:01
    程序猿学社的GitHub,欢迎Star github技术专题 本文已记录到github 文章目录前言需求思路代码 前言 用过mysql的朋友,对mysql性能应该有一定的感悟,数据...使用多线程并发查询每一天的数据在合并。 代码 /** .
  • Java 多线程(超详细)

    千次阅读 2021-01-12 21:14:38
    多线程学习思路:为什么学习线程?为了解决CPU利用率问题,提高CPU利用率。 =》 什么是进程?什么是线程? =》 怎么创建线程?有哪几种方式?有什么特点? =》 分别怎么启动线程? =》 多线程带来了数据安全问题,该...
  • JAVA多线程处理for循环

    千次阅读 2020-12-28 14:37:01
    public static void main(String[] args) { long start = System.currentTimeMillis(); List<Integer> list = new ArrayList(); for (int i = 0;... //定义线程数量为20,可根据服务器配置适当调整大小
  • Java多线程实现TCP网络Socket编程(C/S通信)

    万次阅读 多人点赞 2020-10-26 09:45:38
    本篇详细记录实现java多线程通信,目标达到客户端可以一次接收服务器发送的多条信息,避免阻塞。将客户端接收信息功能独立为一个线程来完成,进一步完善TCP的Socket网络通信,C/S软件架构的程序设计!
  • java多线程创建创建线程的方式Runnable和Callable的区别Thread类中的start()和run()方法有什么区别?什么导致线程阻塞?3. 多线程同步和锁怎么检测一个线程是否持有对象监视器Condition?4. 线程池CyclicBarrier和...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,515,969
精华内容 606,387
关键字:

java多线程

友情链接: Cheat+Engine+6.7.rar