多线程案例之懒汉模式,阻塞队列,定时器,线程池实现


🎈单例模式

懒汉模式

懒汉模式的单例模式 (在调用方法的时候才会创建实例对象),线程安全版的懒汉模式,使用双重 if 提高效率,使用 sychronized 加锁线程安全,volatile 保证内存可见性,外层的 if 读取到的是最新值。

public class ThreadDemo10 {
    // 懒汉模式 (在调用方法的时候才会创建实例对象)
    static class Singleton {
        volatile private Singleton instance = null;
        // 构造方式私有,防止其他代码再创建实例
        private Singleton () {}
        // 双重 if 加 synchronized锁 既保证了线程安全,又保证了效率
        public Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    public static void main(String[] args) {
        Singleton s = new Singleton();
        s.getInstance();
    }
}

饿汉模式

饿汉模式的单例模式,实例的创建出现在 “类加载” 阶段,第一次使用到这个类的时候,就会把这个类的 .class 加载到内存里。

public class ThreadDemo9 {
    // 饿汉模式 (实例的创建出现在类加载的时候) 
    static class Singleton {
        // 先创建一个成员, 保存唯一的一个 Singleton 实例
        private static Singleton instance = new Singleton();
        // 然后把类的构造方法设为 private, 防止其他代码再创建实例
        private Singleton() {}
        // 再提供一个方法, 来获取到这个实例
        public static Singleton getInstance () {
            return instance;
        }
    }

    public static void main(String[] args) {
        // 只能通过 getInstance 的方式获取到该实例.
        // 而无法通过 new 的方式创建新的 Singleton 实例了.
        Singleton s = Singleton.getInstance();
    }
}

🎈阻塞队列

演示 Java 中阻塞队列

队列我们知道是是一种数据结构,一边添加数据,另一边删除数据。阻塞队列就是在队列的基础上实现了类似于 “生产者”,“消费者” 模型,当队列中没有数据的时候,使用 take() 方法取数据就会阻塞(线程等待,不是报错),知道有数据写入的时候才会取到值。同理,当队列满的时候,我们使用 put() 方法添加数据的时候,也会阻塞,直到阻塞队列中有空间了才会添加成功。Java 中提供了现成接口 BlockingQueue 。我们通过实现多个线程来观察效果。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

// 阻塞队列
public class ThreadDemo11 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        // 创建生产者模型
        Thread producer = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        queue.put(i);
                        System.out.println("producer 生产数字"+i);
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        producer.start();
        // 创建消费者模型
        Thread customer = new Thread() {
            @Override
            public void run() {
                while (true){
                    try {
                        int elem = queue.take();
                        System.out.println("customer 消费数字"+elem);
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        };
        customer.start();
        producer.join();
        customer.join();
    }
}

结果如下图:当设置生产速度和消费速度相同时,那么在队列中就会产生这样的一个效果。

模拟实现一个阻塞队列

// 基于数组实现一个普通的队列
// 改进成阻塞队列
public class ThreadDemo12 {

    static class myBlockingQueue {
        private int[] elem = new int[100];
        // 从头部取元素
        private int head = 0;
        // 从尾部存元素
        private int tail = 0;
        // 表示当前队列元素个数
        private int size = 0;
        // 创建一个锁对象
        private Object locker = new Object();

        // 入队列
        public void put(int value) throws InterruptedException {
            synchronized(locker) {
                // 队列已经满了,阻塞等待,等待队列不为空的时候,再插入
                while (size == elem.length) {
                    locker.wait();
                }
                elem[tail] = value;
                tail++;
                // 处理 tail 超出边界
                if (tail >= elem.length) {
                    tail = 0;
                }
                size++;
                locker.notifyAll();
            }
        }
        // 出队列
        public Integer tack() throws InterruptedException {
            int value = 0;
            // 队列为空的时候, 也要阻塞等待
            synchronized (locker) {
                if (size == 0) {
                    locker.wait();
                }
                value = elem[head];
                head++;
                if (head >= elem.length) {
                    head = 0;
                }
                size--;
                locker.notifyAll();
            }
            return value;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        myBlockingQueue queue = new myBlockingQueue();
        // 存元素
        Thread producer = new Thread() {
            @Override
            public void run() {
                    for (int i = 0; i <1000; i++) {
                        try {
                            System.out.println("存储了元素: "+i);
                            queue.put(i);
                            sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
            }
        };
        producer.start();
        // 取元素
        Thread customer = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        int ret = queue.tack();
                        System.out.println("取出元素: "+ret);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        customer.start();
        producer.join();
        customer.join();

    }
}

运行结束:

🎈实现定时器(Timer)

定时器(Timer)就相当于一个闹钟,给定时器设定一个任务,约定这个任务在XXX时间之后执行。Timer 提供一个核心接口 schedule ,指定一个任务交给定时器,在一定的时间后来执行这个任务。
1. Timer 中要包含一个 Task 类,每一个 Task 就表示一个具体的任务实例。Task 里面包含一个时间戳(表示什么时候去执行这个任务),还包含一个 Runnable 实例(用来表示任务具体执行的是啥)
2. Timer 里面通过一个带优先级的的阻塞队列,来组织若干个 task。
3. Timer 中还需要一个专门的线程,让这个线程不停的扫描队首元素,看看队首元素是不是可以执行了,如果可以执行,就执行,如果不能执行,就在队列中继续等待

// 定时器
public class ThreadDemo14 {
    // 每个 Task 实例包含一个要执行的任务
    // Task 要放到优先队列中,但是优先队列里面要进行 ”优先级比较“
    static class Task implements Comparable<Task> {
        // 啥时候执行
        private long time;
        // 执行什么
        private Runnable command;
        // 一般设置定时器的时候,传入的时间,都是一个时间间隔
        // 例如写 1000, 意思就是 1000ms 之后再执行
        public Task(Runnable command,long time) {
            this.command = command;
            // 为了后面方便判断,在这里记录一下绝对时间
            this.time = System.currentTimeMillis() + time;
        }
        public void run () {
            command.run();
        }

        @Override
        public int compareTo(Task o) {
            // 时间小的排到前面
            return (int)(this.time - o.time);
        }
    }

    static class Timer {
        // 创建一个带优先级的阻塞队列
        private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        // 使用这个对象来完成线程之间的协调任务
        private Object mailBox = new Object();
        // schedule 方法的功能就是把一个 Task 放到 Timer中
        public void schedule(Runnable command,long after) {
            Task task = new Task(command,after);
            queue.put(task);
            // 当 worker 线程中包含 wait 机制的时候,再安排任务的时候就需要显示的唤醒一下了
            // 此处是为了处理, 插入的新任务比当前队首的任务还要靠前的情况
            synchronized (mailBox) {
                mailBox.notify();
            }
        }

        public Timer() {
            // 创建一个线程,让这个线程去扫描队列的队首元素,看看能不嫩执行
            Thread worker = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        // 取出队首元素,判断一下这个元素能不执行
                        try {
                            Task task = queue.take();
                            long currentTime = System.currentTimeMillis();
                            if (currentTime >= task.time) {
                                // 时间到了,执行任务
                                task.run();
                            } else {
                                // 时间没到,继续等待
                                queue.put(task);
                                synchronized (mailBox) {
                                    mailBox.wait(task.time - currentTime);
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            worker.start();
        }
    }

    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);
            }
        };
        System.out.println("安排任务");
        timer.schedule(command,3000);
    }
}

总结:定时器
目的:让某个任务在某个时间点再执行,不是立刻执行
接口:schedule,把一个任务+时间加入到定时器中
结构:a) Task类,来描述一个任务。b) 带优先级的阻塞队列。c) 线程扫描队首元素。d) mailBox 方止扫描线程忙等

🎈线程池

简单使用线程池

这个是线程池的一个比较简单的用法。实际上线程池还有更复杂的用法,
标准库里 ThreadPool 类,这个类构造方法中提供了很多的参数选项,来控制线程池具体的行为。
Executors 相当于是对 ThreadPool 进行了简单的封装(把很多参数都给定了默认选项)
工作中更常用的是 ThreadPool 这个复杂版本,就可以更精细的来控制线程池的行为~~

public static void main(String[] args) {
        // 创建方式有很多种.
        // 比如可以通过 Exectors 的静态方法来负责创建不同类型的线程池.
        // 创建一个包含 10 个线程的线程池.
        ExecutorService pool = Executors.newFixedThreadPool(10);
        // 创建一个线程数量动态变化的线程池
        // ExecutorService pool2 = Executors.newCachedThreadPool();

        // 创建好了, 就需要往里面放任务.
        for (int i = 0; i < 100; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
        }
    }

实现一个线程池

线程池内部要有哪些结构:
1. 描述一个任务,就使用Runnable即可,只需要知道任务做啥,不需要知道任务啥时候执行
2. 组织很多任务,使用阻塞队列来保存当前 的所有任务
3. 有一些线程,来负责执行阻塞队列中的任务,让这些线程从阻塞队列中取任务并执行,如果阻塞队列为空,就等待
4. 还需要有一个List把当前线程都保存起来,方便管理

public class ThreadDemo16 {
    static class ThreadPool {
        // 1. 使用 Runnable 来描述这个任务,不需要额外的类
        // 2. 组织若干个任务,使用阻塞队列来组织
        private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
        // 3. 描述一个线程,用来进行工作
        static class Worker extends Thread {
            private BlockingQueue<Runnable> queue = null;

            public Worker(BlockingQueue<Runnable> queue) {
                this.queue = queue;
            }

            @Override
            public void run() {
                while (true) {
                    try {
                        Runnable runnable = queue.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        // 4. 把线程组织起来
        private List<Worker> workers = new ArrayList<>();
        // 设置一个线程最大值
        private static final int MAX_WORKERS_COUNT = 10;
        //  核心接口
        public void execute(Runnable command) throws InterruptedException {
            if (workers.size() < MAX_WORKERS_COUNT) {
                // 当前池子里没有足够的线程, 就创建个新的线程
                Worker worker = new Worker(queue);
                worker.start();
                workers.add(worker);
            }
            queue.put(command);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadPool pool = new ThreadPool();
        for (int i = 0; i < 100; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
        }
    }
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值