多线程案例

1.1单例模式

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

1.1.1饿汉模式

类加载的同时, 创建实例

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

这段代码定义了一个单例类,确保Singleton类在整个应用程序中只有一个实例,并提供了一个公共的静态方法来访问这个唯一的实例。

1.1.2懒汉模式-单线程版

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

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

这段代码定义了一个单例类,使用“懒汉式”实现在首次需要时创建 Singleton 的唯一实例,并提供了一个公共的静态方法来访问这个实例。但是,这种实现在多线程环境下可能不安全,需要额外的同步措施来确保线程安全。

1.1.3懒汉模式-多线程版

加上 synchronized 可以改善这里的线程安全问题

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

这段代码实现了一个线程安全的“懒汉式”单例模式,通过同步 getInstance() 方法来确保在多线程环境下的唯一实例创建。然而,这种方法可能对性能有影响,尤其是在 getInstance() 被频繁调用时。

1.1.4懒汉模式-多线程版(改进)

以下代码在加锁的基础上, 做出了进一步改动:

使用双重 if 判定, 降低锁竞争的频率.

给 instance 加上了 volatile.

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

这段代码通过双重检查锁定和 volatile 关键字的使用,实现了一个线程安全的“懒汉式”单例模式。这种实现在首次需要时才创建实例,并通过同步和可见性控制确保在多线程环境下的唯一实例创建。双重检查锁定通常比简单的同步方法具有更好的性能,因为它减少了不必要的同步开销。

1.2阻塞式队列

阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则. 阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

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

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

1.2.1生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等 待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.

2) 阻塞队列也能使生产者和消费者之间 解耦.

1.2.2标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可. BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue. put 方法用于阻塞式的入队列, take 用于阻塞式的出队列. BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

1.3定时器

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

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

定时器的构成: 一个带优先级的阻塞队列

为啥要带优先级呢? 因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.

队列中的每个元素是一个 Task 对象.

Task 中带有一个时间属性.

同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

1.4线程池

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

newFixedThreadPool: 创建固定线程数的线程池

newCachedThreadPool: 创建线程数目动态增长的线程池.

newSingleThreadExecutor: 创建只包含单个线程的线程池.

newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer

实现线程池

核心操作为 submit, 将任务加入线程池中 使用 Worker 类描述一个工作线程.

使用 Runnable 描述一个任务.

使用一个 BlockingQueue 组织所有的任务

每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.

指定一下线程池中的最大线程数 maxWorkerCount;

当当前线程数超过这个最大值时, 就不再新增 线程了.

1.5保证线程安全的思路

1. 使用没有共享资源的模型

2. 适用共享资源只读,不写的模型 :1. 不需要写共享资源的模型 2. 使用不可变对象

3. 直面线程安全(重点):1. 保证原子性 2. 保证顺序性 3. 保证可见性

1.6对比线程和进程

1.6.1 线程的优点

1. 创建一个新线程的代价要比创建一个新进程小得多

2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

3. 线程占用的资源要比进程少很多

4. 能充分利用多处理器的可并行数量

5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

1.6.2进程与线程的区别

1. 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。

2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。

3. 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。

4. 线程的创建、切换及终止效率更高

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值