认识多线程:单例模式

多线程是并发编程的核心概念,也是面试中的高频考点。本文将围绕多线程中的单例模式、阻塞队列及生产者消费者模型展开,结合代码实例帮助大家理解这些基础知识点。

一、单例模式:懒汉与饿汉

单例模式是最常用的设计模式之一,其核心是保证一个类在全局只有唯一实例,并提供一个访问该实例的全局入口。在多线程场景下,实现安全的单例模式需要特别注意线程同步问题。

1. 饿汉模式

饿汉模式的特点是"饿",即类加载时就创建实例,天然线程安全。

java 复制代码
// 饿汉模式
public class SingletonHungry {
    // 类加载时就初始化实例
    private static final SingletonHungry instance = new SingletonHungry();
    
    // 私有构造方法,防止外部实例化
    private SingletonHungry() {}
    
    // 提供全局访问点
    public static SingletonHungry getInstance() {
        return instance;
    }
}

优点 :实现简单,线程安全(类加载过程由JVM保证线程安全)
缺点:如果实例创建成本高且长时间不使用,会造成资源浪费

2. 懒汉模式(线程安全版)

懒汉模式的特点是"懒",即用到时才创建实例。但需要通过同步锁和双重检确保线程安全。

java 复制代码
// 懒汉模式(线程安全版)
public class SingletonLazy {
    // volatile修饰:防止指令重排序导致的空指针问题
    private static volatile SingletonLazy instance=null;
    private static Object locker=new Object();
    // 私有构造方法
    private SingletonLazy() {}
    
    // 双重检查锁定(Double-Checked Locking)
    public static SingletonLazy getInstance() {
        // 第一次检查:避免不必要的锁竞争
        if (instance == null) {
            // 加锁:保证同步创建实例
            synchronized (locker) {
                // 第二次检查:防止多线程同时通过第一次检查后重复创建
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

关键技术点解析

  • 双重if检查:外层if避免每次调用都加锁(提高效率),内层if防止多线程同时进入临界区后重复创建实例
  • volatile修饰instance = new SingletonLazy()实际分为3步(分配内存→初始化对象→赋值引用),volatile可禁止指令重排序,避免其他线程获取到未初始化的实例

二、阻塞队列:并发场景的"缓冲区"

阻塞队列(BlockingQueue)是多线程编程中的重要工具,它提供了一种线程安全的队列操作方式,核心特性是:

  1. 阻塞特性:当队列满时,入队操作阻塞;当队列空时,出队操作阻塞
  2. 削峰填谷:平衡生产者和消费者的处理能力(比如秒杀场景中缓冲突发请求)
  3. 解耦作用:生产者和消费者无需知道对方存在,通过队列间接交互

基于阻塞队列的生产者消费者模型

生产者消费者模型是并发编程中的经典模式,通过阻塞队列实现可大幅简化代码:

java 复制代码
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

public class Demo27 {
    public static void main(String[] args) {
        BlockingDeque<Integer> deque=new LinkedBlockingDeque<>();
        Thread producer=new Thread(()->{
            int n=0;
            while (true){
                try {
                    Thread.sleep(500);
                    deque.put(n);
                    System.out.println("生产"+n);
                    n++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        },"producer");
        Thread consumer=new Thread(()->{
            while (true){
                try {
                    Thread.sleep(1000);
                    Integer n = deque.take();
                    System.out.println("消费" + n);

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"consumer");
        producer.start();
        consumer.start();
    }
}

运行效果

生产者每0.5秒生产一个数据,消费者每1秒消费一个数据.

三、手动实现阻塞队列

理解阻塞队列的内部原理,有助于更好地使用它。我们可以基于数组和ReentrantLock实现一个简单的阻塞队列:

java 复制代码
class MyBlockingQueue {
    private String[] data = null;
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    public MyBlockingQueue(int capacity) {
        data = new String[capacity];
    }

    public void put(String elem) throws InterruptedException {
        synchronized (this) {
            while (size >= data.length) {
                this.wait();
            }
            data[tail++] = elem;
            if (tail >= data.length) {
                tail = 0;
            }
            //tail=(tail+1)%data.length;}
            size++;
            this.notify();
        }

    }

    public String take() throws InterruptedException {
        synchronized (this) {
            while (size == 0) {
                this.wait();
            }
            String ret = data[head];
            head++;
            if (head >= data.length) {
                head = 0;
            }
            size--;
            this.notify();
            return ret;
        }
    }
}

public class Demo28 {
    public static void main(String[] args) {
        MyBlockingQueue queue=new MyBlockingQueue(50);
        Thread producer=new Thread(()->{
            int n=0;
            while (true){
                try {

                    queue.put(n+"");

                    System.out.println("生产元素"+n);
                    n++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }

        });
        Thread consumer=new Thread(()->{
            String n=null;
            while (true){
                try {
                    Thread.sleep(1000);
                    n=queue.take();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("消费元素"+n);

            }
        });
        producer.start();
        consumer.start();
    }
}

核心实现思路

  1. 用数组实现循环队列(通过取模运算处理指针越界)
  2. 用ReentrantLock保证线程安全,避免并发问题
  3. 用两个Condition对象分别处理"队列满"和"队列空"的阻塞等待

总结

本文介绍了多线程中的三个核心知识点:

  1. 单例模式的两种实现(饿汉模式简单安全,懒汉模式需双重检查+volatile保证线程安全)
  2. 阻塞队列的特性及生产者消费者模型(解耦+削峰填谷的关键工具)
  3. 阻塞队列的手动实现(基于锁和条件变量的经典并发编程实践)

掌握这些基础知识,是深入学习多线程编程的基础。实际开发中,JDK提供了丰富的并发工具类(如ArrayBlockingQueueConcurrentHashMap等),理解其底层原理能帮助我们更好地运用它们解决实际问题。

相关推荐
SimonKing2 小时前
你的项目还在用MyBatis吗?或许这个框架更适合你:Easy-Query
java·后端·程序员
货拉拉技术2 小时前
从代码到配置:如何用SQL配置实现数据核对
java·后端
是苏浙2 小时前
零基础入门C语言之数据在内存中的存储
c语言·开发语言
wjs20243 小时前
HTMLCollection 对象
开发语言
程序员小假3 小时前
设计模式了解吗,知道什么是饿汉式和懒汉式吗?
java·后端
清风与日月3 小时前
c#事件委托示例
开发语言·c#
拾忆,想起3 小时前
TCP粘包拆包全解析:数据流中的“藕断丝连”与“一刀两断”
java·网络·数据库·网络协议·tcp/ip·哈希算法
后端小张3 小时前
【JAVA 进阶】穿越之我在修仙世界学习 @Async 注解(深度解析)
java·开发语言·spring boot·后端·spring·注解·原理
Yeats_Liao3 小时前
Go Web 编程快速入门 18 - 附录B:查询与扫描
开发语言·前端·后端·golang