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


相关文章
|
5天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9
|
8天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
5天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
8天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
22 3
|
7天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
8天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
19 1
|
6月前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【2月更文挑战第22天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个主题,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。
58 0
|
6月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
3月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
64 1
|
4月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
92 0