Java多线程案例

简介: Java多线程案例

Java多线程案例


单例模式

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例

饿汉模式:类加载的同时, 创建实例

public class Singleton {
    private static Singleton instance = new Singleton();//类加载时实例化
    private Singleton(){}//构造私有化
    public static Singleton getInstance(){
        return instance;
    }
}


懒汉模式:第一次使用的时候才创建实例

public class Singleton {
    private volatile static Singleton instance = null;//volatile保证内存可见性
    private Singleton(){}//构造私有化
    public static Singleton getInstance(){
        if(instance == null) {//预判断 降低锁竞争
            synchronized (Singleton.class) {
                if(instance == null) {//获取锁后判断 保证数据原子性
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}


阻塞式队列

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则

阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

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

阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型

生产者消费者模式:

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等

待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取

  1. 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
  2. 阻塞队列也能使生产者和消费者之间 解耦


标准库中的阻塞队列:

  1. BlockingQueue 是一个接口,真正实现的类是 LinkedBlockingQueue
  2. put 方法用于阻塞式的入队列, take 用于阻塞式的出队列
  3. BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();


阻塞队列实现:

  1. 通过 “循环队列” 的方式来实现,使用 synchronized 进行加锁控制
  2. put 插入元素的时候, 判定如果队列满了, 就进行 wait;take 取出元素的时候, 判定如果队列为空, 就进行 wait
  3. 注意被唤醒时不一条件不一定还满足, 因为同时可能是唤醒了多个线程,需要循环判断
public class BlockingQueue {
    private int[] items = new int[1000];
    private volatile int head = 0;
    private volatile int tail = 0;
    public void put(int value) throws InterruptedException {
        synchronized (this) {
            while((tail+1)%items.length == head) {
                wait();
            }
            items[tail++] = value;
            tail %= items.length;
            notifyAll();
        }
    }
    public int take() throws InterruptedException {
        int ret;
        synchronized (this) {
            while(head == tail) {
                wait();
            }
            ret = items[head++];
            head %= items.length;
            notifyAll();
        }
        return ret;
    }
}


定时器

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

标准库中的定时器:

标准库中提供了一个 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 对象,Task 中带有一个时间属性, 队首元素就是即将执行的任务
  3. 有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
  4. 插入新执行任务需要唤醒查看队头是否更新
public class Timer {
    static class Task implements Comparable<Task> {
        private Runnable task;//执行的任务
        private long time;//多久执行
        public Task(Runnable task, long time) {
            this.task = task;
            this.time = System.currentTimeMillis() + time;//绝对时间
        }
        public void run() {
            task.run();//调用执行
        }
        @Override
        public int compareTo(Task o) {
            return (int)(time - o.time);//绝对时间小先执行
        }
    }
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<Task>();//储存任务列表
    private Object lock = new Object();
    class worker extends Thread {//worker线程
        @Override
        public void run() {
            while(true) {
                try {
                    Task task = queue.take();
                    long curTime = System.currentTimeMillis();//当前时间
                    if(task.time > curTime) {//不可执行
                        queue.put(task);
                        synchronized (lock) {
                            lock.wait(task.time -curTime);//等待间隔时长
                        }
                    } else {
                        task.run();//可执行任务
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
    public Timer() {
        worker worker = new worker();//创建启动工作线程
        worker.start();
    }
    public void schedule(Runnable comm, long after) {
        Task task = new Task(comm, after);
        queue.put(task);
        synchronized (lock) {
            lock.notify();//唤醒等待线程
        }
    }
}


线程池

线程池使用池化技术,将预先创建好批量线程,等任务到达进行获取执行。

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

标准库中的线程池:

  1. 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池
  2. 返回值类型为 ExecutorService
  3. 通过 ExecutorService.submit 可以注册一个任务到线程池中
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
});


Executors 创建线程池:

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


Executors 本质上是 ThreadPoolExecutor 类的封装,ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定

实现线程池:

  1. 核心操作为 submit, 将任务加入线程池中
  2. 使用 Worker 类描述一个工作线程,使用 Runnable 描述一个任务
  3. 使用一个 BlockingQueue 组织所有的任务,作为生产消费场所
  4. 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行
public class ThreadPool {
    private List<Thread> workers = new ArrayList<>();
    private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    class Worker extends Thread {
        @Override
        public void run() {
            try {
                while(!Thread.interrupted()){
                    Runnable runnable = queue.take();
                    runnable.run();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public ThreadPool(int threadNums) {
        for (int i = 0; i < threadNums; i++) {
            Worker worker = new Worker();
            worker.start();
            workers.add(worker);
        }
    }
    public void submit(Runnable comm) {
        try {
            queue.put(comm);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}


相关文章
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
33 14
|
5天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
34 13
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
109 17
|
1月前
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
118 12
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
3天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
32 20
|
9天前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
2月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
79 1