多线程案例

多线程案例

  • 1. 单例模式
  • 2. 阻塞式队列
  • 3. 定时器
  • 4. 线程池

1. 单例模式

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.分为懒汉式和饿汉式两种

  1. 饿汉式: 类加载的同时, 创建实例.
class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
   }
}
  1. 饿汉式: 类加载的时候不创建实例. 第一次使用的时候才创建实例.
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

显而易见, 上述代码如果在单线程情况下没有问题, 但是在多线程情况下就有可能发生线程安全问题. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例.
所以就需要对上述代码进行改进:

class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
           if (instance == null) {
               instance = new Singleton();
               }
           }
       }
        return instance;
   }
}

2. 阻塞式队列

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则. 是一种线程安全的数据结构, 并且具有以下特性:
当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型.

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.
BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take();

代码实现:

class MyBlockingDeque {
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;
    public void put(int val) throws InterruptedException {
       synchronized (this) {
           while (size == items.length) {
                this.wait();
           }
           items[tail] = val;
           tail++;
           size++;
           if (tail >= items.length) {
               tail = 0;
           }
           this.notify();
       }
    }
    public Integer take() throws InterruptedException {
        int rest = 0;
        synchronized (this) {
            while (size == 0) {
                this.wait();
            }
            rest = items[head];
            head++;
            size--;
            if (head >= items.length) {
                head = 0;
            }
            this.notify();
        }
        return rest;
    }
}

3. 定时器

定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码.

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

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("hello");
   }
}, 3000);

定时器的构成:

  1. 一个带优先级的阻塞队列
    为啥要带优先级呢?
    因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.

  2. 队列中的每个元素是一个 Task 对象.

  3. Task 中带有一个时间属性, 队首元素就是即将

  4. 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

  5. Task 类用于描述一个任务(作为 Timer 的内部类). 里面包含一个 Runnable 对象和一个 time(毫秒时间戳),这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.

class MyTake implements Comparable<MyTake> {
    private Runnable runnable;
    private long time;

    public MyTake(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }

    public long getTime() {
        return time;
    }
    public void run() {
        runnable.run();
    }
    @Override
    public int compareTo(MyTake o) {
        return (int) (this.time - o.time);
    }
}
  1. Timer 类 是主体部分
 private Thread t = null;
    public MyTimer() {
        t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (this) {
                        MyTake myTake = queue.take();
                        long cur = System.currentTimeMillis();
                        if (cur < myTake.getTime()) {
                            queue.put(myTake);
                            this.wait(myTake.getTime() - cur);//while 一直转会消耗资源所以可以使用wait使线程阻塞
                        }else {
                            myTake.run();

                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
    PriorityBlockingQueue<MyTake> queue = new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable, int after) {
        MyTake myTake = new MyTake(runnable,System.currentTimeMillis() + after);
        queue.put(myTake);
        synchronized (this) {
            this.notify();//因为插入的任务等待时间可能比之前堆顶元素的时间早所以每次插入需要唤醒线程
        }
    }

4. 线程池

标准库中的线程池
使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
返回值类型为 ExecutorService
通过 ExecutorService.submit 可以注册一个任务到线程池中.

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
   }
});

Executors 创建线程池的几种方式
newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数目动态增长的线程池.
newSingleThreadExecutor: 创建只包含单个线程的线程池.
newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
Executors 本质上是 ThreadPoolExecutor 类的封装.
代码实现:

private BlockingDeque<Runnable> blockingDeque = new LinkedBlockingDeque<>();

    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                while (true) {
                    Runnable run = null;
                    try {
                        run = blockingDeque.take();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    run.run();
                }
            });
            t.start();
        }
    }
    public void submit(Runnable runnable) {
        try {
            blockingDeque.put(runnable);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

你可能感兴趣的:(单例模式)