多线程详解【单例模式、阻塞队列、定时器、线程池】

目录

案例一(线程安全的单例模式):

案例二(阻塞队列):

案例三(定时器):

案例四(线程池):


案例一(线程安全的单例模式):

实现一个线程安全的单例模式

关键要点:

        1. 合适的位置加锁

        2. 双重 if 判定,(是否要加锁,是否要创建实例)

        3. volatile

  • 饿汉模式(使用private staitc在类加载的时候创建实例)线程安全
  • 懒汉模式(在调用getInstance的时候才真正创建实例)线程不安全
    // 单例模式完全写法
    class SingletonDataSource {
        // 懒汉模式
        private static volatile SingletonDataSource instance = null;
        
        private SingletonDataSource() {
            
        }
        
        public static SingletonDataSource getInstance() {
            // 判断是否要加锁(还没实例化)
            if (instance == null) {
                // 进行加锁
                synchronized (SingletonDataSource.class) {
                    // 判断是否要实例化
                    if (instance == null) {
                        // 实例化
                        instance = new SingletonDataSource();
                    }
                }
            }
            return instance;
        }
    }

案例二(阻塞队列):

消息队列(本质上是功能更丰富的阻塞队列)

阻塞队列:先进先出

  1. 线程安全的队列
  2. 带有“阻塞功能”

优点:

  1. 解耦合性强
  2. 缓冲(“削峰填谷”)
// 实现一个阻塞队列
class MyBlockingQueue {
    // 队列的初始大小
    private int[] items = new int[100];
    // 记录队列中有效元素的个数
    private int size = 0;
    // 记录队首的位置
    private int head = 0;
    // 记录队尾的位置
    private int tail = 0;
    // 锁对象
    private Object locker = new Object();

    // 入队列
    public void put(int value) throws InterruptedException {
        synchronized (locker) {
            if (size == items.length) {
                // 队列满了
                // return;
                // 此处的等待条件是队列满了,当队列不满就唤醒
                locker.wait();
            }
            items[tail] = value;
            tail++;
            if (tail >= items.length) {
                tail = 0;
            }
            size++;
            // 唤醒 take 的阻塞等待
            locker.notify();
        }
    }

    // 出队列
    public Integer take() throws InterruptedException {
        synchronized (locker) {
            if (size == 0) {
                // return null;
                // 队列为空的时候,再次进行取元素,就需要阻塞等待
                locker.wait();
            }
            int ret = items[head];
            head++;
            if (head >= items.length) {
                head = 0;
            }
            size--;
            // 取走元素成功,唤醒依次 put
            locker.notify();
            return ret;
        }
    }
}

public class Demo2 {
    // 使用这个队列作为交易场所
    private static MyBlockingQueue queue = new MyBlockingQueue();
    public static void main(String[] args) throws InterruptedException {
        // 实现一个简单的生产者消费者模型
        // 两个线程,一个作为生产者,一个作为消费者
        Thread producer = new Thread(() -> {
            int n = 1;
            while (true) {
                try {
                    System.out.println("生产者生产了:" + n);
                    queue.put(n);
                    n++;
                    // 给生产者加个 sleep,让生成的慢,消费的快
                    // 此时大部分情况下是空的,消费者就在阻塞等待,生成一个立马消费一个
                    // Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();

        Thread customer = new Thread(() -> {
            while (true) {
                int n = 0;
                try {
                    n = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者消费了:" + n);
                // 给消费者加个 sleep,让生产的快,消费的慢
                // 此时大部分情况下队列是满的,生产者就很多时间在阻塞等待
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();
    }
}

案例三(定时器):

库中自带的 Timer 定时器

// 库中的定时器
public class Demo3 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // schedule , TimerTask 重写 run 方法
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // 在这里描述具体的任务
                System.out.println("hello timer");
            }
        }, 3000);
        System.out.println("main");
    }
}

自己实现一个定时器

// 实现一个简单的定时器
class MyTimer {
    // 使用一个内部类表示当前的一个任务
    static class Task implements Comparable{
        // 要执行的任务是什么
        private Runnable runnable;
        // 什么时间去执行,此处的 time 使用绝对的时间戳表示
        private long time;

        public Task(Runnable runnable, long after) {
            this.runnable = runnable;
            this.time = System.currentTimeMillis() + after;
        }

        public void run() {
            runnable.run();
        }

        @Override
        public int compareTo(Task o) {
            // 重写比较方法 this - o 是把时间短的放前面
            // o - this 时间大的放前面
            return (int)(this.time - o.time);
        }
    }

    // 把若干个 Task 放入堆中
    private PriorityBlockingQueue tasks = new PriorityBlockingQueue<>();

    // 通过这个方法来往定时器中注册一个任务
    // 告知 定时器 after 毫秒之后,执行这个 runnable 里的 run 方法
    public void schedule(Runnable runnable, long after) {
        Task task = new Task(runnable, after);
        tasks.put(task);
    }

    private Object locker = new Object();

    // 创建一个扫描线程,扫描线程不停的取队首元素,判定任务是否可以执行
    // MyTimer 实例化的时候,创建线程
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    // 取队首元素,判定时间是不是到了
                    Task task = tasks.take();
                    long curTime = System.currentTimeMillis();
                    if (curTime < task.time) {
                        // 时间没到,把任务放回去
                        tasks.put(task);
                        // 此处的 locker 存在的价值是为了等待
                        synchronized (locker) {
                            locker.wait(task.time - curTime);
                        }
                    } else {
                        // 时间到了,就执行这个任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
public class Demo4 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }, 3000);
        System.out.println("main");
    }
}

案例四(线程池):

内核态 vs 用户态

Java标准库内置的线程池:ThreadPollExecutor

手动实现简化版线程池:

class MyThreadPool {
    // 1. 描述任务,直接使用 Runnable
    // 2. 组织任务,使用一个阻塞队列来存放若干个任务
    private BlockingDeque queue = new LinkedBlockingDeque<>();
    // 3. 还需要描述一个工作线程是啥样的
    static class Worker extends Thread {
        // 当前这里的 worker 线程有好几个,这些线程共享一个任务队列
        // 通过这个线程的构造方法,把上面创建好的任务队列给传到线程里面,方便线程去取任务
        private BlockingDeque queue = null;

        public Worker(BlockingDeque queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            // 一个线程要做的工作
            // 反复的从队列中读取任务,然后执行任务
            while (true) {
                Runnable task = null;
                try {
                    // 如果任务队列不为空,此时就能立即取出一个任务并执行
                    // 如果任务队列为空,就hi产生阻塞,阻塞到有人加入新的任务为止
                    task = queue.take();
                    task.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 4. 需要组织若干个工作线程
    private List workerList = new ArrayList<>();

    // 5. 搞一个构造方法,指定一下有多少个线程在线程池中
    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {
            Worker worker = new Worker(queue);
            // 启动线程,先跑起来,再保存到数组
            worker.start();
            workerList.add(worker);
        }
    }

    // 6. 实现一个 submit 来注册任务到线程池中
    public void submit(Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public class Demo5 {
    public static void main(String[] args) {
        MyThreadPool pool = new MyThreadPool(10);
        for (int i = 0; i < 30; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
            
        }
    }
}

你可能感兴趣的:(你必须知道的计算机,java,开发语言,多线程,线程安全)