0
点赞
收藏
分享

微信扫一扫

[Java EE] 多线程(三):线程安全问题(上)

郝春妮 2024-04-28 阅读 33

1.定时器

1.1 定时器是什么

定时器也是软件开发中的一个重要组件.

类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定好的代码.

在标准库里,也是有现成的定时器的实现的 

import java.util.Timer;
import java.util.TimerTask;

// 定时器
public class Demo25 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // 给定时器安排了一个任务, 预定在 xxx 时间去执行.
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        }, 3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        }, 1000);
        System.out.println("程序启动!");
    }
}

 1.2 如何实现定时器

mport java.util.PriorityQueue;

// 通过这个类, 描述了一个任务
class MyTimerTask implements Comparable<MyTimerTask> {
    // 要有一个要执行的任务
    private Runnable runnable;
    // 还要有一个执行任务的时间
    private long time;

    // 此处的 delay 就是 schedule 方法传入的 "相对时间"
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        // 这样的写法, 就是让队首元素是最小时间的值
        // 到底是谁 - 谁, 不要背!! 你可以试试!!
        return (int) (this.time - o.time);
        // 如果是想让队首元素是最大时间的值
        // return o.time - this.time;
    }

    public long getTime() {
        return time;
    }

    public Runnable getRunnable() {
        return runnable;
    }
}

// 咱们自己搞的定时器
class MyTimer {
    // 使用一个数据结构, 保存所有要安排的任务.
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    // 使用这个对象作为锁对象.
    private Object locker = new Object();

    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            queue.offer(new MyTimerTask(runnable, delay));
            locker.notify();
        }
    }

    // 搞个扫描线程.
    public MyTimer() {
        // 创建一个扫描线程
        Thread t = new Thread(() -> {
            // 扫描线程, 需要不停的扫描队首元素, 看是否是到达时间.
            while (true) {
                try {
                    synchronized (locker) {
                        // 不要使用 if 作为 wait 的判定条件, 应该使用 while
                        // 使用 while 的目的是为了在 wait 被唤醒的时候, 再次确认一下条件.
                        while (queue.isEmpty()) {
                            // 使用 wait 进行等待.
                            // 这里的 wait, 需要由另外的线程唤醒.
                            // 添加了新的任务, 就应该唤醒.
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        // 比较一下看当前的队首元素是否可以执行了.
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            // 当前时间已经达到了任务时间, 就可以执行任务了
                            task.getRunnable().run();
                            // 任务执行完了, 就可以从队列中删除了.
                            queue.poll();
                        } else {
                            // 当前时间还没到任务时间, 暂时不执行任务.
                            // 暂时先啥都不干, 等待下一轮的循环判定了.
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

public class Demo26 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        }, 3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        }, 2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        }, 1000);
        System.out.println("程序开始执行");
    }
}

 2.线程池

线程诞生的意义,是因为进程的创建/销毁, 太重量了(比较慢)有对比,才有伤害,和进程比,线程,是更快了,但是如果进一步提高创建销毁的频率, 线程的开销也不能忽视了!

两种典型的办法,进一步提高效率:

1.协程(轻量级线程)

相比于线程,把系统调度的过程,给省略了.(程序猿手工调度当下,一种比较流行的并发编程的手段. 但是在 Java 圈子里,协程还不够流行.

2.线程池

线程池最大的好处就是减少每次启动、销毁线程的损耗。

2.1 线程池的使用 

 2.2 线程池的实现

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPool {
    // 任务队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

    // 通过这个方法, 把任务添加到队列中
    public void submit(Runnable runnable) throws InterruptedException {
        // 此处咱们的拒绝策略, 相当于是第五种策略了. 阻塞等待~~ (这是下策)
        queue.put(runnable);
    }

    public MyThreadPool(int n) {
        // 创建出 n 个线程, 负责执行上述队列中的任务.
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                // 让这个线程, 从队列中消费任务, 并进行执行.
                try {
                    Runnable runnable = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }
}

public class Demo28 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            int id = i;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务: " + id);
                }
            });
        }
    }
}

主线:

线程概念 -> Thread 用法 ->线程安全问题 ->wait notify -> 线程案例

举报

相关推荐

0 条评论