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

目录

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

案例二(阻塞队列):

案例三(定时器):

案例四(线程池):


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

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

关键要点:

        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<Task>{
        // 要执行的任务是什么
        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<Task> 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<Runnable> queue = new LinkedBlockingDeque<>();
    // 3. 还需要描述一个工作线程是啥样的
    static class Worker extends Thread {
        // 当前这里的 worker 线程有好几个,这些线程共享一个任务队列
        // 通过这个线程的构造方法,把上面创建好的任务队列给传到线程里面,方便线程去取任务
        private BlockingDeque<Runnable> queue = null;

        public Worker(BlockingDeque<Runnable> 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<Worker> 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");
                }
            });
            
        }
    }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
阻塞队列线程池的搭配使用是为了提高程序的性能和效率,通过避免不必要的线程创建和销毁,减少资源消耗。阻塞队列线程池中起到任务缓存和通信的作用,它允许生产者(添加任务的线程)和消费者(执行任务的工作线程)之间进行有效的协作。当阻塞队列满时,添加任务的线程会被阻塞,直到队列中有空闲位置;当队列为空时,工作线程会被阻塞,直到队列中有新任务加入。 以下是阻塞队列线程池搭配使用的一些优点: 1. **提高响应速度**:线程池可以重用现有线程,减少线程创建和销毁的开销,从而提高性能和响应速度。 2. **避免资源过度消耗**:线程的创建和销毁是一个相对重量级的操作,线程池通过重用线程来执行多个任务,避免了资源的过度消耗。 3. **提高线程的可管理性**:线程池可以对线程进行统一的分配、调优和监控,提高了线程的可管理性。 4. **避免线程竞争死锁**:阻塞队列提供了等待/通知功能,用于线程间的通信,从而避免了线程竞争死锁的情况发生。 在使用阻塞队列线程池时,需要注意以下几点: 1. 选择合适的阻塞队列类型,如ArrayBlockingQueue、LinkedBlockingQueue等,根据不同的应用场景选择不同的队列特性。 2. 合理配置线程池的参数,如核心线程数、最大线程数、队列容量等,以确保线程池能够高效运行。 3. 考虑线程池的拒绝策略,当阻塞队列满时,需要有合适的策略来处理新添加的任务,如抛出异常、丢弃任务或创建新线程等。 相关问题: 1. 什么是阻塞队列? 2. 什么是线程池? 3. 阻塞队列线程池中的作用是什么?

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值