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);
        }
    }
}


相关文章
|
8天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
38 6
|
21天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
16天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
16天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
40 3
|
17天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
21天前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
28 2
|
21天前
|
监控 Java 开发者
Java线程管理:守护线程与本地线程的深入剖析
在Java编程语言中,线程是程序执行的最小单元,它们可以并行执行以提高程序的效率和响应性。Java提供了两种特殊的线程类型:守护线程和本地线程。本文将深入探讨这两种线程的区别,并探讨它们在实际开发中的应用。
27 1
|
22天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
54 1
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
58 1
C++ 多线程之初识多线程
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
27 3