栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程案例

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程案例

文章目录
  • 单例模式
    • 饿汉模式
    • 懒汉模式
  • 阻塞队列
  • 定时器
  • 线程池

单例模式

这是一种常见的“设计模式”,当在某些情况下,不需要多个实例,就可以使用单例模式来解决,如果想要尝试创建多个实例,代码就会报错。

可以有两种方式:
饿汉模式:当类加载的时候就会立刻实例化。
懒汉模式:类加载时不会实例化,当第一次调用这个类的时候才会去实例化。(可以仔细拼一下为啥叫这两个名字,一个急得想马上创建,一个等啊等啊不到万不得已需要使用了才实例化)

饿汉模式

类加载的时候就会立刻实例化

static class Singleton{
        //这里的构造方法使用private,在类外就无法new这个实例了
        private Singleton() {
        }

        //这里使用static,表示和实例无关,只与类有关
        private static Singleton instance = new Singleton();
        //gerInstance是获取实例的唯一方法
        public static Singleton gerInstance() {
            return instance;
        }
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.gerInstance();
        Singleton s2 = Singleton.gerInstance();
        System.out.println(s1 == s2);
    }

在main方法中,看起来是创建了两个实例,但是我们的打印结果是true,说明这两个实例其实是一个。

懒汉模式

类加载的时候不会实例,只有在第一次调用这个类的时候才会去实例化

static class Singleton {
        private Singleton() {
        }

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

可以观察一下这个代码,当多线程同时调用getInstance方法时,是需要分成几步的。读取instance中的内容,判断是否为null,如果为null,就new实例。返回实例的地址。根据线程安全的思想,可以看出这个代码会导致线程不安全。

1、给getInstance加锁,保证原子性

static class Singleton {
        private Singleton() {
        }

        private static Singleton instance = null;
        public static Singleton getInstance() {//也可以把锁加在这一行,只不过是锁粒度大一点
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
            return instance;
        }
    }

仔细分析,可以发现,如果把实例化已经创建好之后,再去调用getInstance就属于线程安全了。只有在实例化之前是线程不安全的。就可以只在实例化之前加锁,实例化之后就可以不用加锁了,这样就降低了锁的粒度

2、再次进行判断,降低锁粒度

static class Singleton {
        private Singleton() {
        }

        private static Singleton instance = null;
        public static Singleton getInstance() {//也可以把锁加在这一行,只不过是锁粒度大一点
            if (instance == null) {//这个判断是为了只在实例化之前调用加锁,降低锁的粒度
                synchronized (Singleton.class) {
                    if(instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

这个代码还存在一点线程不安全的问题,多线程在调用getInstance,先加锁的线程在修改instance,而后加锁的线程在读数据,这样就会存在内存可见性的安全问题

3、加上volatile,解决内存可见行的问题。

static class Singleton {
        private Singleton() {
        }

        private volatile static Singleton instance = null;
        public static Singleton getInstance() {//也可以把锁加在这一行,只不过是锁粒度大一点
            if (instance == null) {//这个判断是为了只在实例化之前调用加锁,降低锁的粒度
                synchronized (Singleton.class) {
                    if(instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
阻塞队列

这是并发变成的一个重要的组件,可以帮助实现“生产者-消费者模型”。生产者和消费者之间是不能直接联系的,需要通过一个阻塞队列来通信。生产者生产元素,直接放进阻塞队列中,消费者也直接从阻塞队列中取元素。

如果生产者生产的快了,阻塞队列满了,继续生产元素,就会阻塞队列,一直到有消费者来消费元素,才能继续去生产。
如果消费者消费的快了,阻塞队列就会空,继续消费元素,也会阻塞队列,一直到有生产者生产元素,消费者才能去消费。

这样来平衡生产者和消费者

1、先实现一个基础的队列操作,阻塞队列只需要入队列和出队列操作。

static class BlockingQueue{
        private int[] array = new int[100];//使用数组来实现循环队列
        private int head = 0;//头节点下标
        private int tail = 0;//尾节点下标
        private int size = 0;//数组元素的实例个数

        //入队列
        public void put(int value) {
            array[tail] = value;
            tail++;
            if(tail == array.length) {
                tail = 0;
            }
            size++;
        }

        //出队列
        public int take() {
            int ret = array[head];
            head++;
            if(head == array.length) {
                head = 0;
            }
            size--;
            return ret;
        }
    }

2、需要保证操作的原子性,加锁

static class BlockingQueue{
        private int[] array = new int[100];//使用数组来实现循环队列
        private int head = 0;//头节点下标
        private int tail = 0;//尾节点下标
        private int size = 0;//数组元素的实例个数

        //入队列
        public void put(int value) {
            synchronized (this) {
                array[tail] = value;
                tail++;
                if(tail == array.length) {
                    tail = 0;
                }
                size++;
            }
        }

        //出队列
        public int take() {
            int ret;
            synchronized (this) {
                ret = array[head];
                head++;
                if(head == array.length) {
                    head = 0;
                }
                size--;
            }
            return ret;
        }
    }

3、实现阻塞队列的特点,加上wait(),notify()。
入队列操作如果满了,就需要等待,直到有出队列的元素之后才能继续入队列。出队列操作如果空了,也需要等待,直到有入队列的元素之后才能继续出队列。(使用wait()操作时,如果需要用if,建议可以改成while)

static class BlockingQueue{
        private int[] array = new int[100];//使用数组来实现循环队列
        private int head = 0;//头节点下标
        private int tail = 0;//尾节点下标
        private int size = 0;//数组元素的实例个数

        //入队列
        public void put(int value) throws InterruptedException {
            synchronized (this) {
                while (size == array.length) {
                    wait();
                }
                array[tail] = value;
                tail++;
                if(tail == array.length) {
                    tail = 0;
                }
                size++;
                notify();
            }
        }

        //出队列
        public int take() throws InterruptedException {
            int ret;
            synchronized (this) {
                while (size == 0) {
                    wait();
                }
                ret = array[head];
                head++;
                if(head == array.length) {
                    head = 0;
                }
                size--;
                notify();
            }
            return ret;
        }
    }

4、入队列和出队列操作都会去获取size的值,会出现内存可见性的问题,加上volatile。(为了安全起见,我给三个变量都加上了volatile)

//阻塞队列
    static class BlockingQueue{
        private int[] array = new int[100];//使用数组来实现循环队列
        private volatile int head = 0;//头节点下标
        private volatile int tail = 0;//尾节点下标
        private volatile int size = 0;//数组元素的实例个数

        //入队列
        public void put(int value) throws InterruptedException {
            synchronized (this) {
                while (size == array.length) {
                    wait();
                }
                array[tail] = value;
                tail++;
                if(tail == array.length) {
                    tail = 0;
                }
                size++;
                notify();
            }
        }

        //出队列
        public int take() throws InterruptedException {
            int ret;
            synchronized (this) {
                while (size == 0) {
                    wait();
                }
                ret = array[head];
                head++;
                if(head == array.length) {
                    head = 0;
                }
                size--;
                notify();
            }
            return ret;
        }
    }
定时器

在某些业务逻辑中,我们并不需我有些逻辑立刻就执行,需要让他等待一段时间之后再去执行。

定时器的构成:
1、需要一个类来描述这个类需要干什么,以及记录这个类应该什么时间来执行。
2、需要一个阻塞优先队列,来组织很多个任务。
3、需要一个扫描线程,循环检测队列中的任务是否需要执行。
4、需要一个方法,将所有的任务安排到队列中

 class Task implements Comparable{
        private Runnable command;//借助run方法来具体描述任务
        private Long time;
        public Task(Runnable command,Long after) {
            this.command = command;
            this.time = System.currentTimeMillis() + after;
        }

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

        @Override
        public int compareTo(Task o) {
            return (int) (this.time - o.time);
        }
    }

     class Worker extends Thread {
        private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
        private Object mailBox;

        public Worker(PriorityBlockingQueue queue,Object mailBox) {
            this.queue = queue;
            this.mailBox = mailBox;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Task task = queue.take();
                    Long time = System.currentTimeMillis();
                    if(task.time > time) {
                        //时间没到再把任务放回到队列中
                        queue.put(task);
                        synchronized (mailBox) {
                            mailBox.wait(task.time - time);
                        }
                    }else {
                        //时间到了运行这个任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
  class Timer {
        private Object mailBox = null;
        //1、构建一个类来描述这段逻辑(使用Runnable中的run方法)
        //2、使用阻塞优先队列来组织这些任务
        private PriorityBlockingQueue queue = new PriorityBlockingQueue();
        //3、使用一个线程取出队首元素,查看是否到时间
        public Timer (){
            Worker worker = new Worker(queue,mailBox);
            worker.start();
        }
        //4、需要一个接口,将这些任务放入安排起来
        public void schedule(Runnable command,Long after) {
            Task task = new Task(command,after);
            queue.put(task);
            synchronized (mailBox) {
                mailBox.notify();
            }

        }
    }
线程池

在一个项目中会使用到很多的线程,一直的创建和销毁线程会有很大的开销,我们可以使用线程池来避免这些开销。线程池中包含了一些线程,我们可以直接去使用。

主要有两个核心操作:
1、execute:把任务加入到线程池中
2、shotDown:销毁线程池中的所有线程

线程池的构成:
1、需要一个类来表示工作的线程
2、需要一个类来表示线程的执行任务(借助Runnable中的run方法)
3、使用阻塞队列来组织若干个任务
4、使用顺序表来组织若干个线程

class Worker extends Thread{
    private BlockingQueue queue = null;

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

    @Override
    public void run() {
        //try包裹while语句只要出现任何的异常就会立刻退出
        try {
            while (!Thread.currentThread().isInterrupted()) {
                Runnable command = (Runnable) queue.take();
                command.run();
            }
        } catch (InterruptedException e) {
            System.out.println("线程被终止");
            e.printStackTrace();
        }
    }
}
 class MyThreadPool{
    //使用阻塞队列来组织若干个任务
    private BlockingQueue queue = new linkedBlockingDeque<>();
    //使用List来组织若干个线程
    private List workers = new ArrayList<>();
    private static final int maxWorkerCount = 10;

    //把一个任务加到线程池中
     public void execute(Runnable runnable) throws InterruptedException {
         //如果线程池中数量少,就创建新线程。如果多了就不创建
         if(queue.size() < maxWorkerCount) {
             Worker worker = new Worker(queue);
             worker.start();
             workers.add(worker);
         }
         queue.put(runnable);
     }

     //销毁线程池中的所有线程
     public void shotDown() throws InterruptedException {
         for (Worker worker : workers) {
             worker.interrupt();
         }

         for (Worker worker : workers) {
             worker.join();
         }
     }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/658541.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号