Java并发模式和设计策略

引言

小伙伴们,今天小黑要和咱们聊聊Java并发编程的那些事儿。在现代软件开发中,高效地处理多任务是一个不可或缺的能力。特别是对于服务成千上万用户的应用,能够同时处理多个操作不仅是一个加分项,简直是必备技能了!

但说实话,Java并发编程就像是一门艺术,既美丽又充满挑战。为什么这么说呢?首先,它能让咱们的应用跑得更快,处理更多的任务。但与此同时,如果处理不当,它也可能让整个应用崩溃,或者出现各种难以预料的问题。

所以,小黑在这里要和咱们一起探讨一下Java并发编程的奥秘,看看怎样才能既享受它带来的便利,又避免那些潜在的坑。

并发编程的基础

讲到并发编程,咱们首先得搞明白“并发”和“并行”的区别。简单来说,「并发」是指多个任务在同一时间段内执行,而「并行」则是多个任务在同一时刻同时执行。听起来差不多,但其实区别大了去了。

在Java世界里,线程是并发的基石。每个线程都像是一个小小的工人,它们在JVM里并行工作,各司其职。但是,线程之间的协作并非易事。想象一下,如果两个线程同时试图修改同一个数据,事情就会变得复杂。

为了解决这种问题,Java提供了各种同步机制,比如锁和内存模型。锁,就像是一把钥匙,确保在某一时刻只有一个线程可以访问特定的资源。而Java内存模型(JMM),则确保了线程间的可见性和有序性,保证了一个线程对共享变量的修改,其他线程能够及时看到。

来,看个简单的例子:

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

这里,increment方法用synchronized关键字修饰,确保每次只有一个线程能够进入这个方法,从而安全地增加count的值。

但这只是冰山一角。并发编程的世界远比这复杂。接下来,小黑会带咱们深入到Java并发编程的更多细节和模式里去。别担心,虽然听起来有点吓人,但只要咱们一步一个脚印地走,就能慢慢掌握它的精髓。

PS: 小黑收集整理了一份超级全面的复习面试资料包,在这偷偷分享给你~
链接:https://sourl.cn/CjagkK 提取码:yqwt

Java并发模式

生产者-消费者模式

这个模式,就像是餐厅里的厨师和食客。厨师(生产者)负责制作食物,食客(消费者)则负责消费。在编程世界里,咱们也经常遇到类似的场景,比如一个线程生成数据,另一个线程处理这些数据。

来看个例子:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class Producer implements Runnable {
    private BlockingQueue<Integer> queue;

    Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                System.out.println("Produced: " + i);
                queue.put(i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

class Consumer implements Runnable {
    private BlockingQueue<Integer> queue;

    Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Integer value = queue.take();
                System.out.println("Consumed: " + value);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

        Thread producerThread = new Thread(new Producer(queue));
        Thread consumerThread = new Thread(new Consumer(queue));

        producerThread.start();
        consumerThread.start();
    }
}

在这个例子中,ProducerConsumer分别是生产者和消费者,它们通过一个共享的BlockingQueue进行通信。

读写锁模式

当咱们在处理并发数据时,经常会遇到这样的情况:读操作比写操作频繁得多。为了优化这种场景,就有了读写锁模式。这种模式允许多个线程同时读取数据,但在写入数据时,则需要独占锁。

来看个简单的读写锁实现:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class SharedResource {
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    private int resourceValue;

    public void increment() {
        lock.writeLock().lock();
        try {
            resourceValue++;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getValue() {
        lock.readLock().lock();
        try {
            return resourceValue;
        } finally {
            lock.readLock().unlock();
        }
    }
}

public class ReadWriteLockExample {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();

        // 创建多个读取线程和写入线程
        // ...
    }
}

在这个例子中,SharedResource类使用ReadWriteLock来实现读写分离,提高了在高并发情况下的性能。

单例模式

在并发环境下,单例模式确保一个类只创建一个实例,并且提供一个全局访问点。这在处理资源共享,比如数据库连接或配置管理时尤为重要。但在多线程环境中,要确保这个实例不会被多次创建。

来看个双检锁的单例实现示例:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

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

这里使用了volatile关键字和双重检查锁定,以确保线程安全且性能优化。

观察者模式

观察者模式在并发环境中也很有用,特别是在事件驱动或消息传递系统中。在这种模式下,被观察对象一旦状态变化,就会通知所有观察者对象。

看下面的代码示例:

import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update(String message);
}

class ConcreteObserver implements Observer {
    @Override
    public void update(String message) {
        System.out.println("Received message: " + message);
    }
}

class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

public class ObserverPatternExample {
    public static void main(String[] args) {
        Subject subject = new Subject();
        Observer observer = new ConcreteObserver();

        subject.attach(observer);
        subject.notifyObservers("Hello, Observer Pattern!");
    }
}

在这个示例中,Subject类维护一个观察者列表,当发生某些事件时,它会通知所有观察者。

工作窃取模式

在并发编程的世界里,平衡每个线程的负载是一项挑战。有时候,一些线程可能忙得不可开交,而其他线程则闲得发慌。这时,「工作窃取模式」就闪亮登场了。它允许空闲的线程从忙碌的线程那里偷取任务来执行。

在Java中,咱们可以利用ForkJoinPool来实现这个模式。下面是一个简单的示例:

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

class SimpleRecursiveTask extends RecursiveTask<Integer> {
    private int simulatedWork;

    public SimpleRecursiveTask(int simulatedWork) {
        this.simulatedWork = simulatedWork;
    }

    @Override
    protected Integer compute() {
        if (simulatedWork > 100) {
            System.out.println("Parallel execution needed because of the huge task: " + simulatedWork);
            SimpleRecursiveTask task1 = new SimpleRecursiveTask(simulatedWork / 2);
            SimpleRecursiveTask task2 = new SimpleRecursiveTask(simulatedWork / 2);

            task1.fork();
            task2.fork();

            int solution = 0;
            solution += task1.join();
            solution += task2.join();

            return solution;
        } else {
            System.out.println("No need for parallel execution, small task: " + simulatedWork);
            return 2 * simulatedWork;
        }
    }
}

public class WorkStealingExample {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
        SimpleRecursiveTask task = new SimpleRecursiveTask(400);
        System.out.println(forkJoinPool.invoke(task));
    }
}

在这个例子中,SimpleRecursiveTask是一个简单的任务,它会根据工作量的大小决定是否需要拆分为更小的任务。ForkJoinPool负责管理这些任务,包括任务的分配和工作窃取。

事件驱动模式

事件驱动模式是另一个在并发编程中非常有用的模式。在这个模式中,程序的流程是由事件决定的,比如用户输入、传感器信号或者消息。这种模式在编写响应式程序时特别有用。

在Java中,咱们可以使用监听器和回调来实现这个模式。下面是一个简单的事件监听器实现:

import java.util.ArrayList;
import java.util.List;

interface EventListener {
    void onEvent(Event e);
}

class Event {}

class EventSource {
    private final List<EventListener> listeners = new ArrayList<>();

    public void registerListener(EventListener listener) {
        listeners.add(listener);
    }

    public void eventOccured(Event e) {
        for (EventListener listener : listeners) {
            listener.onEvent(e);
        }
    }
}

public class EventDrivenExample {
    public static void main(String[] args) {
        EventSource eventSource = new EventSource();
        eventSource.registerListener(e -> System.out.println("Event received: " + e));
        eventSource.eventOccured(new Event());
    }
}

在这个例子中,EventSource是事件的源头,它可以触发事件。当事件发生时,所有注册的EventListener都会接收到通知。

接着上面的话题,小黑还要和咱们聊两个挺酷的并发模式:双检锁模式和线程局部存储模式。

双检锁/双重校验锁模式

这个模式用于创建线程安全的懒汉式单例。所谓懒汉式,就是实例在第一次被使用时才创建。但要小心,如果不正确实现,就可能在多线程环境下产生多个实例。这就是双检锁模式发挥作用的地方。

看看这个例子:

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

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

在这里,小黑使用了两次检查:第一次检查避免不必要的同步,第二次检查确保只有一个实例被创建。同时,instance变量被声明为volatile,防止指令重排序。

线程局部存储模式

线程局部存储(Thread Local Storage, TLS)模式允许咱们为每个线程存储单独的数据副本,从而避免了多线程间的数据共享问题。这在处理像数据库连接或用户会话这样的任务时特别有用。

来看个例子:

public class ThreadLocalExample {
    private static final ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            threadLocalValue.set(1);
            System.out.println("Thread 1: " + threadLocalValue.get());
        });

        Thread thread2 = new Thread(() -> {
            threadLocalValue.set(2);
            System.out.println("Thread 2: " + threadLocalValue.get());
        });

        thread1.start();
        thread2.start();
    }
}

在这个例子中,每个线程都有一个自己的threadLocalValue副本,互不干扰。这种方式特别适合那些需要隔离处理每个线程状态的场景。

有限状态机模式

接下来,小黑带咱们看看另一个在Java并发编程中非常有用的模式:有限状态机(Finite State Machine, FSM)。这个模式,就像是一部机器,它根据输入改变自己的状态。在并发编程中,这个模式特别有用,因为它帮助管理复杂的状态转换,尤其是在多线程环境下。

想象一下,咱们有一个网络连接的类,它可以处于连接、断开或重连等状态。这些状态之间的转换需要精确控制,以避免多个线程造成的混乱。有限状态机就是为此而生的。

来看看怎么用代码实现这个模式:

public class ConnectionState {
    private enum State {
        CONNECTED, DISCONNECTED, RECONNECTING
    }

    private State currentState;

    public ConnectionState() {
        currentState = State.DISCONNECTED;
    }

    public synchronized void connect() {
        if (currentState == State.DISCONNECTED) {
            currentState = State.CONNECTED;
            // 连接逻辑
        }
    }

    public synchronized void disconnect() {
        if (currentState == State.CONNECTED) {
            currentState = State.DISCONNECTED;
            // 断开连接逻辑
        }
    }

    public synchronized void reconnect() {
        if (currentState == State.DISCONNECTED) {
            currentState = State.RECONNECTING;
            // 重连逻辑
        }
    }

    // 其他方法...
}

在这个简单的例子中,ConnectionState类用枚举State来表示不同的状态,并且每个方法都会根据当前的状态来决定是否改变状态。这种方式使得状态转换清晰、可控,极大地减少了并发环境中的复杂性。

总结

小黑今天和咱们一起走过了Java并发编程的几个关键模式。咱们看到了,无论是生产者-消费者模式,还是读写锁模式,亦或是有限状态机,每一种模式都像是并发编程的一块拼图,帮助咱们构建起更稳健、更高效的应用程序。

记住,并发编程并不仅仅是关于线程的启动和停止,更重要的是理解数据之间的交互和状态的管理。通过今天的学习,咱们可以更好地把握这些概念,更加灵活地应用于实际的开发工作中。

在并发编程的世界里,没有一劳永逸的解决方案。随着技术的发展和应用场景的变化,新的模式和理论也在不断涌现。因此,持续学习,保持好奇心和探索精神,是每个Java开发者成长道路上不可或缺的一部分。

希望通过今天的分享,咱们都能在Java并发编程的路上走得更远,构建出更加强大、更加稳定的应用。记得,每一步的进步,都是通往高手之路的重要一环!加油,咱们一起向前!


面对寒冬,我们更需团结!小黑收集整理了一份超级强大的复习面试资料包,也强烈建议你加入我们的Java后端报团取暖群,一起复习,共享各种学习资源,互助成长。无论是新手还是老手,这里都有你的位置。在这里,我们共同应对职场挑战,分享经验,提升技能,闲聊副业,共同抵御不确定性,携手走向更稳定的职业未来。让我们在Java的路上,不再孤单!进群方式以及资料,点击如下链接即可获取!

链接:https://sourl.cn/CjagkK 提取码:yqwt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/217080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【踩坑】解决maven的编译报错Cannot connect to the Maven process. Try again later

背景 新公司新项目, 同事拷给我maven的setting配置文件, 跑项目编译发现maven报 Cannot connect to the Maven process. Try again later. If the problem persists, check the Maven Importing JDK settings and restart IntelliJ IDEA 虽然好像不影响, 项目最终还是能跑起来…

C++ 系列 第四篇 C++ 数据类型上篇—基本类型

系列文章 C 系列 前篇 为什么学习C 及学习计划-CSDN博客 C 系列 第一篇 开发环境搭建&#xff08;WSL 方向&#xff09;-CSDN博客 C 系列 第二篇 你真的了解C吗&#xff1f;本篇带你走进C的世界-CSDN博客 C 系列 第三篇 C程序的基本结构-CSDN博客 前言 面向对象编程(OOP)的…

Linux(14):进程管理

一个程序被加载到内存当中运作&#xff0c;那么在内存内的那个数据就被称为进程(process)。 进程是操作系统上非常重要的概念&#xff0c;所有系统上面跑的数据都会以进程的型态存在。 进程 在 Linux底下所有的指令与能够进行的动作都与权限有关&#xff0c;而系统如何判定权…

Android wifi连接和获取IP分析

wifi 连接&获取IP 流程图 代码流程分析 一、关联阶段 1. WifiSettings.submit – > WifiManager WifiSettings 干的事情比较简单&#xff0c;当在dialog完成ssid 以及密码填充后&#xff0c;直接call WifiManager save 即可WifiManager 收到Save 之后&#xff0c;就开…

JVM:双亲委派(未完结)

类加载 定义 一个java文件从编写代码到最终运行&#xff0c;必须要经历编译和类加载的过程&#xff0c;如下图&#xff08;图源自b站视频up主“跟着Mic学架构”&#xff09;。 编译就是把.java文件变成.class文件。类加载就是把.class文件加载到JVM内存中&#xff0c;得到一…

Shell数组函数:数组(一)

一、数组简介&#xff1a; 变量&#xff1a;用一个固定的字符串&#xff0c;代替一个不固定字符串。数组&#xff1a;用一个固定的字符串&#xff0c;代替多个不固定字符串。 二、类型 普通数组&#xff1a;只能使用整数作为数组索引关联数组&#xff1a;可以使用字符串作为…

【LeetCode热题100】【双指针】三数之和

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例 …

具有五层协议的网络体系结构

目录 一、计算机的网络体系结构 二、五层协议的体系结构 1、物理层 2、数据链路层 3、网络层 4、传输层 5、应用层 三、数据在各层之间传输的过程 一、计算机的网络体系结构 二、五层协议的体系结构 1、物理层 利用传输介质为通信的网络结点之间建立、管理和释放物理连…

电压驻波比

电压驻波比 关于IF端口的电压驻波比 一个信号变频后&#xff0c;从中频端口输出&#xff0c;它的输出跟输入是互异的。这个电压柱波比反映了它输出的能量有多少可以真正的输送到后端连接的器件或者设备。

沐风老师3DMAX随机变换工具RandomTransform插件使用方法详解

3DMAX随机变换工具RandomTransform插件使用方法 3dMax随机变换工具RandomTransform&#xff0c;是一款用MAXScript脚本语言开发的3dsMax小工具&#xff0c;可以随机变换选中的单个或多个对象的位置、角度及大小。 在3dMax中“变换”工具是最常用的工具&#xff08;移动、旋转和…

【hacker送书第8期】Java从入门到精通(第7版)

第8期图书推荐 内容简介编辑推荐作者简介图书目录参与方式 内容简介 《Java从入门到精通&#xff08;第7版&#xff09;》从初学者角度出发&#xff0c;通过通俗易懂的语言、丰富多彩的实例&#xff0c;详细讲解了使用Java语言进行程序开发需要掌握的知识。全书分为4篇共24章&a…

Java生成word[doc格式转docx]

引入依赖 <!-- https://mvnrepository.com/artifact/org.freemarker/freemarker --><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.32</version></dependency> doc…

家政服务预约小程序系统的开发;

家政服务预约小程序系统的开发&#xff0c;既是对传统加盟服务模式的创新&#xff0c;也是家政商家企业营销推广服务的升级。它推动整个家政服务行业实现线上线下深度融合&#xff0c;提升用户消费体验&#xff0c;实现了雇主、服务提供者、家政企业商家三者之间的无缝衔接&…

Spring之AOP理解与应用

1. AOP的认识 面向切面编程&#xff1a;基于OOP基础之上新的编程思想&#xff0c;OOP面向的主要对象是类&#xff0c;而AOP面向的主要对象是切面&#xff0c;在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点&#xff0c;AOP提供了非常强…

阿里云服务器租赁价格表,预算100元到5000元可选配置

阿里云服务器租用费用&#xff0c;阿里云轻量应用服务器2核2G3M带宽轻量服务器一年87元&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;ECS云服务器e系列2核2G配置3M固定带宽99元一年、2核4G配置365元一年、2核8G配置522元一年&#xff0c;阿里云u1服务器2核4G、…

语义分割 DeepLab V1网络学习笔记 (附代码)

论文地址&#xff1a;https://arxiv.org/abs/1412.7062 代码地址&#xff1a;GitHub - TheLegendAli/DeepLab-Context 1.是什么&#xff1f; DeepLab V1是一种基于VGG模型的语义分割模型&#xff0c;它使用了空洞卷积和全连接条件随机&#xff08;CRF&#xff09;来提高分割…

RPG项目01_技能释放

基于“RPG项目01_新输入输出”&#xff0c; 修改脚本文件夹中的SkillBase脚本&#xff1a; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; //回复技能&#xff0c;魔法技能&#xff0c;物理技能…

分布式搜索引擎elasticsearch(二)

1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括: 查询所有:查询出所有数据,一般测试用。例如:match_all 全文检索(full text)查…

d3dx9_43.dll丢失原因以及5个解决方法详解

在电脑使用过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“d3dx9_43.dll缺失”。这个错误提示通常表示我们的电脑上缺少了DirectX的一个组件&#xff0c;而DirectX是游戏和多媒体应用所必需的软件。本文将介绍d3dx9_43.dll缺失对电脑的影响以及其原因…

【从零开始学习Redis | 第六篇】爆改Setnx实现分布式锁

前言&#xff1a; 在Java后端业务中&#xff0c; 如果我们开启了均衡负载模式&#xff0c;也就是多台服务器处理前端的请求&#xff0c;就会产生一个问题&#xff1a;多台服务器就会有多个JVM&#xff0c;多个JVM就会导致服务器集群下的并发问题。我们在这里提出的解决思路是把…