• Java多线程中——部分场景使用实现


    单例模式

    设计模式部分后续也会出,但是可能会比较晚,先鸽一下嘿嘿嘿,
    首先来加深理解一下加锁的作用

    单例模式作用:

    能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。(JDBC 中的 DataSource 实例就只需要一个)

    单例模式实现方法

    饿汉模式

    类加载的同时, 创建实例

    class Singleton {
        private static Singleton instance = new Singleton();
    
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    懒汉模式

    类加载的时候不创建实例. 第一次使用的时候才创建实例

    懒汉模式单线程
    public class 懒汉单线程 {
        private static 懒汉单线程 instance = null; 
        private 懒汉单线程() {
            
        } 
        public synchronized static 懒汉单线程 getInstance() { 
            if (instance == null) { 
                instance = new 懒汉单线程(); 
            }
            return instance; 
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    仔细观察这段代码,好像没什么问题,但是如果在多线程的话,它其实线程不安全,那我们怎么修改呢?看下面:

    懒汉模式多线程
    public class 懒汉多线程 {
        private static 懒汉多线程 instance = null; 
        private 懒汉多线程() {
            
        } 
        public synchronized static 懒汉多线程 getInstance() { 
            if (instance == null) { 
                instance = new 懒汉多线程(); 
            }
            return instance; 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    没错就是你看到那样我们只需要添加synchronized关键字。

    但是,众所周知,加锁 、 解锁是一件开销比较高的事情.,否则StringBuffer。。。而懒汉模式的线程不安全只是发生在首次创建实例的时候.,后续的使用,其实就 不必再进行加锁了,所以我们可以做如下修改:

    public class 懒汉模式多线程改进 {
        private static volatile 懒汉模式多线程改进 instance = null;
    
        private 懒汉模式多线程改进() {
    
        }
    
        public static 懒汉模式多线程改进 getInstance() {
            if (instance == null) {
                synchronized (懒汉模式多线程改进.class) {
                    if (instance == null) {
                        instance = new 懒汉模式多线程改进();
                    }
                }
            }
            return instance;
        }
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    提问:为什么要这么设计?

    解:首先我们要知道不安全的可能是因为多线程调用时,可能会有多个实例来getInstance(),那么我们就给他加锁,然后解决性能问题:外层判断使创建的实例只能有一个因为给他上锁了,但是内存可见性怎么保证呢,万一读缓存呢?所以我们给他加volatile,诺~,完美解决!!!

    想必现在我们对单例模式的线程安全处理已经有了一定理解那么接下来:

    阻塞式队列

    首先顾名思义啦,队列队列了啦,当然遵循队列的先进先出的规则啦。

    当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
    当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

    阻塞队列的一个典型的开发模型:生产消费者模型,开发模型这个东西就也先鸽一下吧
    当然俺会丢下队友吗?显然不会,所以这里还是介绍一下生产消费者模型吧:
    在这里插入图片描述
    既然是阻塞队列的一种应用,他当然遵循阻塞队列的一些特性比如上面那些。

    那么接下来我们看一段生产消费者模型相关代码

    public class 生产消费者模型 {
        public static void main(String[] args) throws InterruptedException {
            BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
            Thread customer = new Thread(() -> {
                while (true) {
                    try {
                        int value = blockingQueue.take();
                        System.out.println("消费元素: " + value);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者");
            customer.start();
            Thread producer = new Thread(() -> {
                Random random = new Random();
                while (true) {
                    try {
                        int num = random.nextInt(1000);
                        System.out.println("生产元素: " + num);
                        blockingQueue.put(num);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者");
            producer.start();
            customer.join();
            producer.join();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    这个代码能看懂吧,应该吧,注意阻塞队列的加入,当然,如果看不懂的话,我们接下来实现一下阻塞队列:

    public class BlockingQueue {
        private int[] items = new int[1000];
        private volatile int size = 0;
        private int head = 0;
        private int tail = 0;
    
        public void put(int value) throws InterruptedException {
            synchronized (this) {
    
                while (size == items.length) {
                    wait();
                }
                items[tail] = value;
                tail = (tail + 1) % items.length;
                size++;
                notifyAll();
            }
        }
    
        public int take() throws InterruptedException {
            int ret = 0;
            synchronized (this) {
                while (size == 0) {
                    wait();
                }
                ret = items[head];
                head = (head + 1) % items.length;
                size--;
                notifyAll();
            }
            return ret;
        }
    
        public synchronized int size() {
            return size;
        }
    
        public static void main(String[] args) throws InterruptedException {
        //既然是psvm啦当然是测试一下用的了啦
            BlockingQueue blockingQueue = new BlockingQueue();
            Thread customer = new Thread(() -> {
                while (true) {
                    try {
                        int value = blockingQueue.take();
                        System.out.println(value);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者");
            customer.start();
            Thread producer = new Thread(() -> {
                Random random = new Random();
                while (true) {
                    try {
                        blockingQueue.put(random.nextInt(10000));
                      
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者");
            producer.start();
            customer.join();
            producer.join();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    定时器

    字面意思就可以理解的大概了吧,程序在定时多久后执行
    先来看一下官方的东西:

    public class 定时器官方 {
        public static void main(String[] args) {
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            }, 5000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用:

    • 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule
    • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒).

    实现定时器

    定时器的构成:

    • 一个带优先级的阻塞队列
    • 队列中的每个元素是一个 Task 对象.
    • Task 中带有一个时间属性, 队首元素就是即将
    • 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
      提问:为啥要带优先级呢?
      解:因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来

    下面我们来看一下实现过程:

    public class Timer {
        static class Task implements Comparable<Task> {
            private Runnable command;
            private long time;
            public Task(Runnable command, long time) {
                this.command = command;
                // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
                this.time = System.currentTimeMillis() + time;
            }
    
            public void run() {
                command.run();
            }
    
            @Override
            public int compareTo(Task o) { // 谁的时间小谁排前面
                return (int) (time - o.time);
            }
        }private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
        
        private Object mailBox = new Object();
    
        class Worker extends Thread {
            @Override
            public void run() {
                while (true) {
                    try {
                        Task task = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (task.time > curTime) { // 时间还没到, 就把任务再塞回去
                            queue.put(task);
                            synchronized (mailBox) {
                                // 指定等待时间wait
                                mailBox.wait(task.time - curTime);
                            }
                        } else {
                            // 时间到了, 可以执行任务
                            task.run();
                        }
                    } catch (
                            InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }
            }
        }
        public Timer() {
            // 启动 worker 线程
            Worker worker = new Worker();
            worker.start();
        }
        public void schedule(Runnable command, long after) {
            Task task = new Task(command, after);
            queue.offer(task);
            synchronized (mailBox) {
                mailBox.notify();
            }
        }
    
        public static void main(String[] args) {
            Timer timer = new Timer();
            Runnable command = new Runnable() {
                @Override
                public void run() {
                    System.out.println("我来了");
                    timer.schedule(this, 3000);
                }
            };
            timer.schedule(command, 3000);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    线程池

    众所周知线程的启动销毁是很费资源的,所以有什么好的办法吗?

    线程池工作原理

    在这里插入图片描述
    Executors 创建线程池的几种方式:

    1. newFixedThreadPool: 创建固定线程数的线程池
    2. newCachedThreadPool: 创建线程数目动态增长的线程池.
    3. newSingleThreadExecutor: 创建只包含单个线程的线程池.
    4. newScheduledThreadPool: 设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer.

    实现线程池

    1. 核心操作为 submit, 将任务加入线程池中
    2. 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
    3. 使用一个 BlockingQueue 组织所有的任务
    4. 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
    5. 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了

    下面我们来看代码:

    
    public class MyThreadPool{
        /**存放线程的集合*/
        private ArrayList<MyThead> threads;
        /**任务队列*/
        private ArrayBlockingQueue<Runnable> taskQueue;
        /**线程池初始限定大小*/
        private int threadNum;
        /**已经工作的线程数目*/
        private int workThreadNum;
    
        private final ReentrantLock mainLock = new ReentrantLock();
    
        public MyThreadPool(int initPoolNum) {
            threadNum = initPoolNum;
            threads = new ArrayList<>(initPoolNum);
            //任务队列初始化为线程池线程数的四倍
            taskQueue = new ArrayBlockingQueue<>(initPoolNum*4);
            workThreadNum = 0;
        }
    
        public void execute(Runnable runnable) {
            try {
                mainLock.lock();
                //线程池未满,每加入一个任务则开启一个线程
                if(workThreadNum < threadNum) {
                    MyThead myThead = new MyThead(runnable);
                    myThead.start();
                    threads.add(myThead);
                    workThreadNum++;
                }
                //线程池已满,放入任务队列,等待有空闲线程时执行
                else {
                    //队列已满,无法添加时,拒绝任务
                    if(!taskQueue.offer(runnable)) {
                        rejectTask();
                    }
                }
            } finally {
                mainLock.unlock();
            }
        }
    
        private void rejectTask() {
            System.out.println("任务队列已满,无法继续添加,请扩大您的初始化线程池!");
        }
        public static void main(String[] args) {
            MyThreadPool myThreadPool = new MyThreadPool(5);
            Runnable task = new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"执行中");
                }
            };
    
            for (int i = 0; i < 20; i++) {
                myThreadPool.execute(task);
            }
        }
    
        class MyThead extends Thread{
            private Runnable task;
    
            public MyThead(Runnable runnable) {
                this.task = runnable;
            }
            @Override
            public void run() {
                //该线程一直启动着,不断从任务队列取出任务执行
                while (true) {
                    //如果初始化任务不为空,则执行初始化任务
                    if(task != null) {
                        task.run();
                        task = null;
                    }
                    //否则去任务队列取任务并执行
                    else {
                        Runnable queueTask = taskQueue.poll();
                        if(queueTask != null)
                            queueTask.run();
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    到目前,基本概念和其的一些简单用法都已实现,后续将详细介绍各种锁、和多线程的运用敬请期待

  • 相关阅读:
    JavaScript中迭代:For循环
    golang语言的gofly快速开发框架如何设置多样的主题说明
    华为云云耀云服务器L实例评测 | 实例场景体验之搭建静态 Web 站点:通过华为云云耀云服务器构建一个静态的 Web 站点
    编译器优化记录(Mem2Reg+SSA Destruction)
    Flink
    插件创建Maven工程
    42.接雨水--dp版本
    知识点滴 - 有关剧本的网站
    数据结构Python版(四)——队列
    【深度学习】笔记1-感知机
  • 原文地址:https://blog.csdn.net/weixin_45696320/article/details/126235353