Java多线程中——部分场景使用实现

Java
251
0
0
2023-05-26
标签   Java多线程

文章目录

单例模式

设计模式部分后续也会出,但是可能会比较晚,先鸽一下嘿嘿嘿,

首先来加深理解一下加锁的作用

单例模式 作用:

能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。( JDBC 中的 DataSource 实例就只需要一个)

单例模式实现方法

饿汉模式

类加载的同时, 创建实例

 class Singleton {
 
     private   static  Singleton instance = new Singleton();

    private Singleton() {
 

    }

    public static Singleton  getInstance () {
 
        return instance;
    }
}  

懒汉模式

类加载的时候不创建实例. 第一次使用的时候才创建实例

懒汉模式单线程

 public class 懒汉单线程 {
 
    private static 懒汉单线程 instance = null; 
    private 懒汉单线程() {
 
        
    } 
    public  synchronized  static 懒汉单线程 getInstance() {
  
        if (instance == null) {
  
            instance = new 懒汉单线程(); 
        }
        return instance; 
    }
}  

仔细观察这段代码,好像没什么问题,但是如果在 多线程 的话,它其实 线程 不安全,那我们怎么修改呢?看下面:

懒汉模式多线程

 public class 懒汉多线程 {
 
    private static 懒汉多线程 instance = null; 
    private 懒汉多线程() {
 
        
    } 
    public synchronized static 懒汉多线程 getInstance() {
  
        if (instance == null) {
  
            instance = new 懒汉多线程(); 
        }
        return instance; 
    }
}  

没错就是你看到那样我们只需要添加synchronized关键字。

但是,众所周知,加锁 、 解锁是一件开销比较高的事情.,否则 StringBuffer 。。。而懒汉模式的线程不安全只是发生在首次创建实例的时候.,后续的使用,其实就 不必再进行加锁了,所以我们可以做如下修改:

 public class 懒汉模式多线程改进 {
 
    private static volatile 懒汉模式多线程改进 instance = null;

    private 懒汉模式多线程改进() {
 

    }

    public static 懒汉模式多线程改进 getInstance() {
 
        if (instance == null) {
 
            synchronized (懒汉模式多线程改进.class) {
 
                if (instance == null) {
 
                    instance = new 懒汉模式多线程改进();
                }
            }
        }
        return instance;
    }
    
}  

提问:为什么要这么设计?

解:首先我们要知道不安全的可能是因为多线程调用时,可能会有多个实例来getInstance(),那么我们就给他加锁,然后解决性能问题:外层判断使创建的实例只能有一个因为给他上锁了,但是内存可见性怎么保证呢,万一读缓存呢?所以我们给他加 volatile ,诺~,完美解决!!!

想必现在我们对单例模式的 线程安全 处理已经有了一定理解那么接下来:

阻塞式队列

首先顾名思义啦,队列队列了啦,当然遵循队列的先进先出的规则啦。

当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.

当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型的开发模型:生产消费者模型,开发模型这个东西就也先鸽一下吧

当然俺会丢下队友吗?显然不会,所以这里还是介绍一下生产消费者模型吧:

既然是阻塞队列的一种应用,他当然遵循阻塞队列的一些特性比如上面那些。

那么接下来我们看一段生产消费者模型相关代码

 public class 生产消费者模型 {
 
    public static  void  main(String[] args) throws Interrupted Exception  {
 
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
         Thread  customer = new Thread(() -> {
 
            while (true) {
 
                try {
 
                    int value = blockingQueue.take();
                    System.out.println("消费元素: " + value);
                } catch (InterruptedException e) {
 
                    e.printStackTrace();
                }
            }
        }, "消费者");
        customer.start();
        Thread producer = new Thread(() -> {
 
            Random random = new Random();
            while (true) {
 
                try {
 
                    int num = random.nextInt(1000);
                    System.out.println("生产元素: " + num);
                    blockingQueue.put(num);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
 
                    e.printStackTrace();
                }
            }
        }, "生产者");
        producer.start();
        customer.join();
        producer.join();
    }
}  

这个代码能看懂吧,应该吧,注意阻塞队列的加入,当然,如果看不懂的话,我们接下来实现一下阻塞队列:

 public class BlockingQueue {
 
    private int[] items = new int[1000];
    private volatile int size = 0;
    private int head = 0;
    private int tail = 0;

    public void put(int value) throws InterruptedException {
 
        synchronized (this) {
 

            while (size == items.length) {
 
                wait();
            }
            items[tail] = value;
             tail  = (tail + 1) % items.length;
            size++;
            notifyAll();
        }
    }

    public int take() throws InterruptedException {
 
        int ret = 0;
        synchronized (this) {
 
            while (size == 0) {
 
                wait();
            }
            ret = items[head];
            head = (head + 1) % items.length;
            size--;
            notifyAll();
        }
        return ret;
    }

    public synchronized int size() {
 
        return size;
    }

    public static void main(String[] args) throws InterruptedException {
 
    //既然是psvm啦当然是测试一下用的了啦
        BlockingQueue blockingQueue = new BlockingQueue();
        Thread customer = new Thread(() -> {
 
            while (true) {
 
                try {
 
                    int value = blockingQueue.take();
                    System.out.println(value);
                } catch (InterruptedException e) {
 
                    e.printStackTrace();
                }
            }
        }, "消费者");
        customer.start();
        Thread producer = new Thread(() -> {
 
            Random random = new Random();
            while (true) {
 
                try {
 
                    blockingQueue.put(random.nextInt(10000));
                  
                } catch (InterruptedException e) {
 
                    e.printStackTrace();
                }
            }
        }, "生产者");
        producer.start();
        customer.join();
        producer.join();
    }
}  

字面意思就可以理解的大概了吧,程序在定时多久后执行

先来看一下官方的东西:

 public class 定时器官方 {
 
    public static void main(String[] args) {
 
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
 
            @Override
            public void run() {
 
                System.out.println("hello");
            }
        }, 5000);
    }
}  

使用:

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

实现定时器

定时器的构成:

  • 一个带优先级的阻塞队列
  • 队列中的每个元素是一个 Task 对象.
  • Task 中带有一个时间属性, 队首元素就是即将
  • 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
  • 提问:为啥要带优先级呢?
  • 解:因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效地把这个 delay 最小的任务找出来

下面我们来看一下实现过程:

 public class Timer {
 
    static class Task implements Comparable<Task> {
 
        private Runnable command;
        private long time;
        public Task(Runnable command, long time) {
 
            this.command = command;
            // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
            this.time = System.currentTimeMillis() + time;
        }

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

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

    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
    
    private Object mailBox = new Object();

    class Worker  extends  Thread {
 
        @Override
        public void run() {
 
            while (true) {
 
                try {
 
                    Task task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if (task.time > curTime) {
  // 时间还没到, 就把任务再塞回去
                        queue.put(task);
                        synchronized (mailBox) {
 
                            // 指定等待时间wait
                            mailBox.wait(task.time - curTime);
                        }
                    } else {
 
                        // 时间到了, 可以执行任务
                        task.run();
                    }
                } catch (
                        InterruptedException e) {
 
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
    public Timer() {
 
        // 启动 worker 线程
        Worker worker = new Worker();
        worker.start();
    }
    public void schedule(Runnable command, long after) {
 
        Task task = new Task(command, after);
        queue.offer(task);
        synchronized (mailBox) {
 
            mailBox.notify();
        }
    }

    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);
            }
        };
        timer.schedule(command, 3000);
    }
}  

众所周知线程的启动销毁是很费资源的,所以有什么好的办法吗?

线程池 工作原理

Executors 创建线程池的几种方式:

  1. newFixedThreadPool: 创建固定线程数的线程池
  2. newCachedThreadPool: 创建线程数目动态增长的线程池.
  3. newSingleThreadExecutor: 创建只包含单个线程的线程池.
  4. newScheduledThreadPool: 设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer.

实现线程池

  1. 核心操作为 submit, 将任务加入线程池中
  2. 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
  3. 使用一个 BlockingQueue 组织所有的任务
  4. 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
  5. 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了

下面我们来看代码:

 public class MyThreadPool{
 
    /**存放线程的集合*/    private ArrayList<MyThead> threads;
    /**任务队列*/    private ArrayBlockingQueue<Runnable> taskQueue;
    /**线程池初始限定大小*/    private int threadNum;
    /**已经工作的线程数目*/    private int workThreadNum;

    private final ReentrantLock main lock  = new ReentrantLock();

    public MyThreadPool(int initPoolNum) {
 
        threadNum = initPoolNum;
        threads = new ArrayList<>(initPoolNum);
        //任务队列初始化为线程池线程数的四倍
        taskQueue = new ArrayBlockingQueue<>(initPoolNum*4);
        workThreadNum = 0;
    }

    public void execute(Runnable runnable) {
 
        try {
 
            mainLock.lock();
            //线程池未满,每加入一个任务则开启一个线程
            if(workThreadNum < threadNum) {
 
                MyThead myThead = new MyThead(runnable);
                myThead.start();
                threads.add(myThead);
                workThreadNum++;
            }
            //线程池已满,放入任务队列,等待有空闲线程时执行
            else {
 
                //队列已满,无法添加时,拒绝任务
                 if (!taskQueue.offer(runnable)) {
 
                    rejectTask();
                }
            }
        } finally {
 
            mainLock.unlock();
        }
    }

    private void rejectTask() {
 
        System.out.println("任务队列已满,无法继续添加,请扩大您的初始化线程池!");
    }
    public static void main(String[] args) {
 
        MyThreadPool myThreadPool = new MyThreadPool(5);
        Runnable task = new Runnable() {
 
            @Override
            public void run() {
 
                System.out.println(Thread.currentThread().getName()+"执行中");
            }
        };

        for (int i = 0; i < 20; i++) {
 
            myThreadPool.execute(task);
        }
    }

    class MyThead extends Thread{
 
        private Runnable task;

        public MyThead(Runnable runnable) {
 
            this.task = runnable;
        }
        @Override
        public void run() {
 
            //该线程一直启动着,不断从任务队列取出任务执行
            while (true) {
 
                //如果初始化任务不为空,则执行初始化任务
                if(task != null) {
 
                    task.run();
                    task = null;
                }
                //否则去任务队列取任务并执行
                else {
 
                    Runnable queueTask = taskQueue.poll();
                    if(queueTask != null)
                        queueTask.run();
                }
            }
        }
    }
}  

到目前,基本概念和其的一些简单用法都已实现,后续将详细介绍各种锁、和多线程的运用敬请期待 。