网站建设需要固定ip地址吗南阳网站排名价格

当前位置: 首页 > news >正文

网站建设需要固定ip地址吗,南阳网站排名价格,百度指数搜索榜度指数,网页设计跟网站建设避免共享的设计模式 不变性#xff08;Immutability#xff09;模式#xff0c;写时复制#xff08;Copy-on-Write#xff09;模式#xff0c;线程本地存储#xff08;Thread-Specific Storage#xff09;模式本质上都是为了避免共享。 1、使用时需要注意不变性模式…避免共享的设计模式 不变性Immutability模式写时复制Copy-on-Write模式线程本地存储Thread-Specific Storage模式本质上都是为了避免共享。 1、使用时需要注意不变性模式的属性的不可变性。 2、写时复制模式需要注意拷贝的性能问题。 3、线程本地存储模式需要注意异步执行问题。 不变性Immutability模式——想破坏也破坏不了 “多个线程同时读写同一共享变量存在并发问题”这里的必要条件之一是读写如果只有读而没 有写是没有并发问题的。解决并发问题其实最简单的办法就是让共享变量只有读操作而没有写操作。这个办法如此重要以至于被上升到了一种解决并发问题的设计模式不变性Immutability模式。 不变性模式是一种创建不可变对象的设计模式即对象一旦创建后就不能再进行修改。在多线程 环境下使用不可变对象可以避免线程安全问题并提高程序的性能和可读性。 使用不变性模式可以带来多种好处例如 1、线程安全性不可变对象在多线程环境下不需要进行同步操作可以避免线程安全问题。 2、可读性不可变对象的状态在创建后不可修改可以更加清晰地表达对象的含义和作用。 3、性能由于不可变对象的状态不可变可以进行更加有效的缓存和优化操作。 3、可测试性不可变对象对单元测试非常友好可以更容易地进行测试和验证。 不变性模式虽然有很多优点但也有一些限制。例如不可变对象的状态一旦创建后就无法修改需要重新创建一个新的对象。因此在需要频繁修改对象状态的场景下不可变对象可能不太适用。同时在不可变对象之间存在引用关系的情况下需要注意对象状态的变化。 使用场景 不变性模式适用于需要确保对象状态不变的场景例如 1、缓存在缓存系统中需要缓存一些数据以避免重复计算和频繁访问。由于缓存数据是共享的为了避免数据被修改通常使用不变性模式来确保缓存数据的不变性。 2、值对象在一些系统中需要定义一些值对象来表示一些常量或者不可变的对象。使用不变性模式可以确保这些值对象的状态不变从而避免出现错误和不一致。 3、配置信息在一些系统中需要读取一些配置信息来配置系统参数和行为。由于配置信息通常是不变的可以使用不变性模式来确保配置信息的不变性。 如何实现不变性模式 不变性模式的主要思想是通过将对象的状态设置为final和私有并提供只读方法来保证对象的不可变性。在创建不可变对象时需要确保对象的所有属性都是不可变的即在创建后不会被修改。同时还需要注意不可变对象之间的引用关系以避免出现对象的状态变化。 jdk中很多类都具备不可变性例如经常用到的 String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性这些对象的线程安全性都是靠不可变性来保证的。它们都严格遵守了不可变类的三点要求类和属性都是 final 的所有方法均是只读的。 代码示例 /*** 短信服务商信息/ public class SmsInfo {/** 短信服务商请求url/private String url;/** 每次发送短信内容的最大字节数/private Integer maxSizeInBytes;public SmsInfo(String url, Integer maxSizeInBytes) {this.url url;this.maxSizeInBytes maxSizeInBytes;}public String getUrl() {return url;}public void setUrl(String url) {this.url url;}public void setMaxSizeInBytes(Integer maxSizeInBytes) {this.maxSizeInBytes maxSizeInBytes;}Overridepublic String toString() {return SmsInfo{ url url \ , maxSizeInBytes maxSizeInBytes };} }public class SmsDemo {public static void main(String[] args) throws InterruptedException {// 创建短信服务商的信息SmsInfo smsInfo new SmsInfo(https://www.aliyun.com, 180);new Thread(new Runnable() {Overridepublic void run() {smsInfo.setUrl(https://cloud.tencent.com);try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}smsInfo.setMaxSizeInBytes(200);}}, 线程1).start();// 线程2读取短信服务商信息new Thread(new Runnable() {Overridepublic void run() {System.out.println(线程2获取短信服务商信息 smsInfo);}}, 线程2).start();} }执行结果 线程2获取短信服务商信息SmsInfo{urlhttps://cloud.tencent.com, maxSizeInBytes180} 发现线程2读取的最大字节数不正确。 因为配置一般都是不变的所以改造之后的代码示例 /** 短信服务商信息/ public class SmsInfo {/** 短信服务商请求url/private final String url;/** 每次发送短信内容的最大字节数*/private final Integer maxSizeInBytes;public SmsInfo(String url, Integer maxSizeInBytes) {this.url url;this.maxSizeInBytes maxSizeInBytes;}public String getUrl() {return url;}public SmsInfo update(String url, Integer maxSizeInBytes) {return new SmsInfo(url, maxSizeInBytes);}public Integer getMaxSizeInBytes() {return maxSizeInBytes;}Overridepublic String toString() {return SmsInfo{ url url \ , maxSizeInBytes maxSizeInBytes };} }public class SmsDemo {public static void main(String[] args) throws InterruptedException {// 创建短信服务商的信息SmsInfo smsInfo new SmsInfo(https://www.aliyun.com, 180);new Thread(new Runnable() {Overridepublic void run() {smsInfo.update(https://cloud.tencent.com, 200);}}, 线程1).start();// 线程2读取短信服务商信息new Thread(new Runnable() {Overridepublic void run() {System.out.println(线程2获取短信服务商信息 smsInfo);}}, 线程2).start();} }执行结果 线程2获取短信服务商信息SmsInfo{urlhttps://www.aliyun.com, maxSizeInBytes180} 将属性改为final如果要修改可以加个update方法返回新的对象这样获取结果就还是原来的。 使用 Immutability 模式的注意事项 在使用 Immutability 模式的时候需要注意以下两点 1、对象的所有属性都是 final 的并不能保证不可变性。 2、不可变对象也需要正确发布。 在 Java 语言中final 修饰的属性一旦被赋值就不可以再修改但是如果属性的类型是普通对象那么这个普通对象的属性是可以被修改的。所以在使用 Immutability 模式的时候一定要确认保持不变性的边界在哪里是否要求属性对象也具备不可变性。 下面的代码中Bar 的属性 foo 虽然是 final 的依然可以通过 setAge() 方法来设置 foo 的属性 age。 class Foo {int age 0;String name abc; } final class Bar {final Foo foo;Bar(Foo foo) {this.foo foo;}void setAge(int a) {foo.age a;} } 不可变对象虽然是线程安全的但是并不意味着引用这些不可变对象的对象就是线程安全的。 下面的代码中Foo 具备不可变性线程安全但是类 Bar 并不是线程安全的类 Bar 中持有对 Foo 的引用 foo对 foo 这个引用的修改在多线程中并不能保证可见性和原子性。 final class Foo {final int age 0;final String name abc; } class Bar {Foo foo;void setFoo(Foo f) {this.foo f;} } 写时复制Copy-on-Write模式 在多线程环境下Copy-on-Write模式可以提高共享数据的并发性能。该模式的基本思想是在共享数据被修改时先将数据复制一份然后对副本进行修改最后再将副本替换为原始的共享数据。通过这种方式可以避免多个线程同时访问同一个共享数据造成的竞争和冲突。         不可变对象的写操作往往都是使用 Copy-on-Write 方法解决的当然 Copy-on-Write 的应用领域并不局限于 Immutability 模式。         Copy-on-Write 才是最简单的并发解决方案很多人都在无意中把它忽视了。它是如此简单以至于 Java 中的基本数据类型 String、Integer、Long 等都是基于 Copy-on-Write 方案实现的。         Copy-on-Write 缺点就是消耗内存每次修改都需要复制一个新的对象出来好在随着自动垃圾回收GC算法的成熟以及硬件的发展这种内存消耗已经渐渐可以接受了。所以在实际工作中如果写操作非常少读多写少的场景可以尝试使用 Copy-on-Write。 使用场景 在Java中CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器它们背后的设计思想就是 Copy-on-Write通过 Copy-on-Write 这两个容器实现的读操作是无锁的由于无锁所以将读操作的性能发挥到了极致。         Copy-on-Write 在操作系统领域也有广泛的应用。类 Unix 的操作系统中创建进程的 API 是 fork()传统的 fork() 函数会创建父进程的一个完整副本例如父进程的地址空间现在用到了 1G 的内存那么fork() 子进程的时候要复制父进程整个进程的地址空间占有 1G 内存给子进程这个过程是很耗时的。而 Linux 中fork() 子进程的时候并不复制整个进程的地址空间而是让父子进程共享同一个地址空间只用在父进程或者子进程需要写入的时候才会复制地址空间从而使父子进程拥有各自的地址空间。         Copy-on-Write 最大的应用领域还是在函数式编程领域。函数式编程的基础是不可变性Immutability所以函数式编程里面所有的修改操作都需要 Copy-on-Write 来解决。         像一些RPC框架还有服务注册中心也会利用Copy-on-Write设计思想维护服务路由表。路由表是典型的读多写少而且路由表对数据的一致性要求并不高一个服务提供方从上线到反馈到客户端的路由表里即便有 5 秒钟延迟很多时候也都是能接受的。 线程本地存储Thread-Specific Storage 模式——没有共享就没有伤害 线程本地存储模式用于解决多线程环境下的数据共享和数据隔离问题。该模式的基本思想是为每个线程创建独立的存储空间用于存储线程私有的数据。通过这种方式可以保证线程之间的数据隔离和互不干扰。在 Java 标准类库中ThreadLocal 类实现了该模式。         线程本地存储模式本质上是一种避免共享的方案由于没有共享所以自然也就没有并发问题。如果你需要在并发场景中使用一个线程不安全的工具类最简单的方案就是避免共享。避免共享有两种方案一种方案是将这个工具类作为局部变量使用另外一种方案就是线程本地存储模式。这两种方案局部变量方案的缺点是在高并发场景下会频繁创建对象而线程本地存储方案每个线程只需要创建一个工具类的实例所以不存在频繁创建对象的问题。 使用场景 线程本地存储模式通常用于以下场景 保存上下文信息在多线程环境中每个线程都有自己的执行上下文包括线程的状态、环境变量、运行时状态等。线程本地存储可以用来保存这些上下文信息使得每个线程都可以独立地访问和修改自己的上下文信息。管理线程安全对象在多线程环境中共享对象通常需要进行同步操作以避免竞争条件。但是有些对象是线程安全的可以被多个线程同时访问而不需要同步操作。线程本地存储可以用来管理这些线程安全对象使得每个线程都可以独立地访问自己的对象实例而不需要进行同步操作。实现线程特定的行为有些应用程序需要在每个线程中执行特定的行为例如跟踪日志、统计数据、授权访问等。线程本地存储可以用来实现这些线程特定的行为使得每个线程都可以独立地执行自己的行为逻辑而不需要与其他线程进行协调。 需要注意的是线程本地存储虽然可以提高性能但也可能会导致内存泄漏和数据一致性问题。因 此在使用线程本地存储时需要仔细考虑数据的生命周期和线程的使用情况避免出现潜在的问 题。 注意在线程池中使用ThreadLocal 需要避免内存泄漏和线程安全的问题。 多线程版本的if模式 守护挂起Guarded Suspension模式和避免执行Balking模式属于多线程版本的if模式 1、守护挂起模式需要注意性能。 2、避免重复执行模式需要注意竞态问题。 守护挂起Guarded Suspension模式 守护挂起模式是通过让线程等待来保护实例的安全性即守护-挂起模式。在多线程开发中常常为了提高应用程序的并发性会将一个任务分解为多个子任务交给多个线程并行执行而多个线程之间相互协作时仍然会存在一个线程需要等待另外的线程完成后继续下一步操作。而Guarded Suspension模式可以帮助我们解决上述的等待问题。 守护挂起模式允许多个线程对实例资源进行访问但是实例资源需要对资源的分配做出管理。 守护挂起模式也常被称作 Guarded Wait 模式、Spin Lock 模式因为使用了 while 循环去等 待它还有一个更形象的非官方名字多线程版本的 if。 1、有一个结果需要从一个线程传递到另一个线程让他们关联同一个 GuardedObject。 2、如果有结果不断从一个线程到另一个线程那么可以使用消息队列。 3、JDK 中join 的实现、Future 的实现采用的就是此模式。 4、因为要等待另一方的结果因此归类到同步模式。 等待唤醒机制的规范实现。此模式依赖于Java线程的等待唤醒机制 1、synchronizedwait/notify/notifyAll 2、reentrantLockCondition(await/singal/singalAll) 3、caspark/unpark 使用场景 1、多线程环境下多个线程访问相同实例资源从实例资源中获得资源并处理。 2、实例资源需要管理自身拥有的资源并对请求线程的请求作出允许与否的判断。 避免执行Balking模式 Balking是“退缩不前”的意思。如果现在不适合执行这个操作或者没必要执行这个操作就停止处理直接返回。当流程的执行顺序依赖于某个共享变量的场景可以归纳为多线程if模式。Balking 模式常用于一个线程发现另一个线程已经做了某一件相同的事那么本线程就无需再做了直接结束返回。         Balking模式是一种多个线程执行同一操作A时可以考虑的模式在某一个线程B被阻塞或者执行其他操作时其他线程同样可以完成操作A而当线程B恢复执行或者要执行操作A时因A已被执行而无需线程B再执行从而提高了B的执行效率。         Balking模式和Guarded Suspension模式一样存在守护条件如果守护条件不满足则中断处理这与Guarded Suspension模式不同Guarded Suspension模式在守护条件不满足的时候会一直等待至可以运行。 使用场景 1、sychronized轻量级锁膨胀逻辑 只需要一个线程膨胀获取monitor对象。 2、DCL单例实现。 3、服务组件的初始化。 如何实现Balking模式 1、锁机制 synchronized reentrantLock 2、cas 3、对于共享变量不要求原子性的场景可以使用volatile 需要快速放弃的一个最常见的场景是各种编辑器提供的自动保存功能。自动保存功能的实现逻辑一 般都是隔一定时间自动执行存盘操作存盘操作的前提是文件做过修改如果文件没有执行过修改操作就需要快速放弃存盘操作。 多线程分工模式 Thread-Per-Message 模式、Worker Thread 模式和生产者 - 消费者模式属于多线程分工模式。 1、Thread-Per-Message 模式需要注意线程的创建销毁以及是否会导致OOM。 2、Worker Thread 模式需要注意死锁问题提交的任务之间不要有依赖性。 3、生产者 - 消费者模式可以直接使用线程池来实现。 Thread-Per-Message 模式——最简单实用的分工方法 Thread-Per-Message 模式就是为每个任务分配一个独立的线程这是一种最简单的分工方法。 应用场景 Thread-Per-Message 模式的一个最经典的应用场景是网络编程里服务端的实现服务端为每个客户端请求创建一个独立的线程当线程处理完请求后自动销毁这是一种最简单的并发处理网络请求的方法。         Thread-Per-Message 模式作为一种最简单的分工方案Java 中使用会存在性能缺陷。在 Java 中的线程是一个重量级的对象创建成本很高一方面创建线程比较耗时另一方面线程占用的内存也比较大。所以为每个请求创建一个新的线程并不适合高并发场景。为了解决这个缺点Java 并发包里提供了线程池等工具类。         在其他编程语言里例如 Go 语言基于轻量级线程实现 Thread-Per-Message 模式就完全没有问题。         对于一些并发度没那么高的异步场景例如定时任务采用 Thread-Per-Message 模式是完全没有问题的。 Worker Thread模式——如何避免重复创建线程 要想有效避免线程的频繁创建、销毁以及 OOM 问题就不得不提 Java 领域使用最多的 WorkerThread 模式。Worker Thread 模式可以类比现实世界里车间的工作模式车间里的工人有活儿了大家一起干没活儿了就聊聊天等着。Worker Thread 模式中 Worker Thread 对应到现实世界里其实指的就是车间里的工人。 应用场景 Worker Thread 模式能避免线程频繁创建、销毁的问题而且能够限制线程的最大数量。Java语言里可以直接使用线程池来实现 Worker Thread 模式线程池是一个非常基础和优秀的工具类甚至有些大厂的编码规范都不允许用 new Thread() 来创建线程必须使用线程池。 生产者 - 消费者模式——用流水线的思想提高效率 Worker Thread 模式类比的是工厂里车间工人的工作模式。但其实在现实世界工厂里还有一种流水线的工作模式类比到编程领域就是生产者 - 消费者模式。         生产者 - 消费者模式的核心是一个任务队列生产者线程生产任务并将任务添加到任务队列中而消费者线程从任务队列中获取任务并执行。 生产者 - 消费者模式的优点 支持异步处理 场景用户注册后需要发注册邮件和注册短信。传统的做法有两种 1.串行的方式2.并行方式 引入消息队列将不是必须的业务逻辑异步处理。 解耦 场景用户下单后订单系统需要通知库存系统扣减库存。 可以消除生产者生产与消费者消费之间速度差异 在计算机当中创建的线程越多CPU进行上下文切换的成本就越大所以我们在编程的时候创建的线程并不是越多越好而是适量即可采用生产者和消费者模式就可以很好的支持我们使用适量的线程来完成任务。         如果在某一段业务高峰期的时间里生产者“生产”任务的速率很快而消费者“消费”任务速率很慢由于中间的任务队列的存在也可以起到缓冲的作用我们在使用MQ中间件的时候经常说的削峰填谷也就是这个意思。 过饱问题解决方案 在实际生产项目中会有些极端的情况导致生产者/消费者模式可能出现过饱的问题。单位时间内生产者生产的速度大于消费者消费的速度导致任务不断堆积到阻塞队列中队列堆满只是时间问题。 思考是不是只要保证消费者的消费速度一直比生产者生产速度快就可以解决过饱问题 我们只要在业务可以容忍的最长响应时间内把堆积的任务处理完那就不算过饱。 什么是业务容忍的最长响应时间 比如埋点数据统计前一天的数据生成报表第二天老板要看的你前一天的数据第二天还没处理完那就不行这样的系统我们就要保证消费者在24小时内的消费能力要比生产者高才行。 场景一消费者每天能处理的量比生产者生产的少如生产者每天1万条消费者每天只能消费5千条。 解决办法消费者加机器 原因生产者没法限流因为要一天内处理完只能消费者加机器。 场景二消费者每天能处理的量比生产者生产的多。系统高峰期生产者速度太快把队列塞爆了 解决办法适当的加大队列 原因消费者一天的消费能力已经高于生产者那说明一天之内肯定能处理完保证高峰期别把队列塞满就好 场景三消费者每天能处理的量比生产者生产的多。条件有限或其他原因队列没法设置特别大。系统高峰期生产者速度太快把队列塞爆了 解决办法生产者限流 原因消费者一天的消费能力高于生产者说明一天内能处理完队列又太小那只能限流生产者让高峰期塞队列的速度慢点。