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

网站一定要备案才能设计国外优秀网站案例

网站一定要备案才能设计,国外优秀网站案例,网站配置域名,wordpress 连接被重置常见的锁策略 这里讨论的锁策略,不仅仅局限于 Java 乐观锁 vs 悲观锁 锁冲突: 两个线程尝试获取一把锁#xff0c;一个线程能获取成功,另一个线程阻塞等待。 乐观锁: 预该场景中,不太会出现锁冲突的情况。后续做的工作会更少。 悲观锁: 预测该场景,非常容易出现锁冲突。后…常见的锁策略 这里讨论的锁策略,不仅仅局限于 Java 乐观锁 vs 悲观锁 锁冲突: 两个线程尝试获取一把锁一个线程能获取成功,另一个线程阻塞等待。 乐观锁: 预该场景中,不太会出现锁冲突的情况。后续做的工作会更少。 悲观锁: 预测该场景,非常容易出现锁冲突。后续做的工作会更多。 重量级锁 vs 轻量级锁 重量级锁: 加锁的开销是比较大的(花的时间多占用系统资源多) 轻量级锁: 加锁开销比较小的(花的时间少占用系统资源少) 一个悲观锁,很可能是重量级锁(不绝对)。一个乐观锁,也很可能是轻量级锁(不绝对) 悲观乐观是在加锁之前对锁冲突概率的预测决定工作的多少。重量轻量是在加锁之后,考量实际的锁的开销。正是因为这样的概念存在重合针对一个具体的锁,可能把它叫做乐观锁,也可能叫做轻量级锁。 自旋锁Spin Lockvs 挂起等待锁 自旋锁是轻量级锁的一种典型实现 在用户态下,通过自旋的方式**(while 循环)**实现类似于加锁的效果的这种锁,会消耗一定的 cpu 资源,但是可以做到最快速度拿到锁。 挂起等待锁是重量级锁的一种典型实现 通过内核态借助系统提供的锁机制。当出现锁冲突的时候使冲突的线程出现挂起**(阻塞等待)**。挂起等待不会消耗CPU这种方式,消耗的 cpu 资源是更少的。也就无法保证第一时间拿到锁。 读写锁 VS 互斥锁 读写锁把读操作加锁和写操作加锁分开了 一个事实: 多线程同时去读同一个变量,不涉及到线程安全问题。 如果两个线程, 一个线程读加锁,另一个线程也是读加锁不会产生锁竞争。(并发执行效率更高了) 如果两个线程一个线程写加锁,另一个线程也是写加锁,会产生锁竞争。 如果两个线程, 一个线程写加锁,另一个线程读加锁也会产生锁竞争。 实际开发中,读操作的频率,往往比写操作,高很多。Java 标准库里,也提供了现成的读写锁。ReentrantReadWriteLock 。 ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行 加锁解锁。ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进 行加锁解锁。 互斥锁Synchronized 这种只有单纯的加锁解锁两个操作。 公平锁 vs 非公平锁 公平锁是遵守先来后到的锁。B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁. 非公平锁: 不遵守 “先来后到”. B 和 C 都有可能获取到锁 。 非公平锁看起来是概率均等,但是实际上是不公平.(每个线程阻塞时间是不一样的)。 操作系统自带的锁 (pthread mutex) 属于是非公平锁。要想实现公平锁就需要有一些额外的数据结构来支持。比如需要有办法记录每个线程的阻塞等待时间。 可重入锁 vs 不可重入锁 如果一个线程,针对一把锁,连续加锁两次,会出现死锁就是不可重入锁; 不会出现死锁,就是可重入锁。 public synchronized void increase(){synchronized(locker){count;} }1.调用方法,先针对 this 加锁. 此时假设加锁成功了 2.接下来往下执行到 代码块 中的 synchronized。此时,还是针对 this 来进行加锁。 此时就会产生锁竞争.当前 this 对象已经处于加锁状态了。此时该线程就会阻塞一直阻塞到锁被释放,才能有机会拿到锁。 此时,由于 this 的锁没法释放。这个代码就卡在这里了因此这个线程就僵住了。此时就产生了死锁。 这里的关键在于,两次加锁,都是“同一个线程。第二次尝试加锁的时候,该线程已经有了这个锁的权限了 这个时候不应该加锁失败的,不应该阻塞等待的。 不可重入锁这把锁不会保存,是哪个线程对它加的锁。只要它当前处于加锁状态之后,收到了加锁”这样的请求 就会拒绝当前加锁。而不管当下的线程是哪个。就会产生死锁。 可重入锁是会让这个锁保存,是哪个线程加上的锁。后续收到加请求之后,就会先对比一下,看看加锁的线程是不是当前持有自己这把锁的线程,这个时候就可以灵活判定了。 synchronized本身是一个可重入锁 实际上不会产生上述的死锁情况。 死锁 死锁概念 死锁多个线程同时被阻塞它们中的一个或者全部都在等待某个资源被释放。多个线程被无限期阻塞导致线程不可能正常终止。 死锁的三种典型情况: 一个线程,一把锁,但是是不可重入锁.该线程针对这个锁连续加锁两次,就会出现死锁两个线程两把锁.这两个线程先分别获取到一把锁然后再同时尝试获取对方的锁。N 个线程 M 把锁哲学家就餐问题。 两个线程两把锁问题 就相当于一个在疫情时期的一个段子。健康码坏了程序员要进去修但是程序员不能出示健康码不能进去修要想有健康码就得修好了才能出示。 public class ThreadDemo {private static Object locker1 new Object();private static Object locker2 new Object();public static void main(String[] args) {Thread t1 new Thread(()-{synchronized (locker1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println(t1两把锁加锁成功);}}});Thread t2 new Thread(()-{synchronized (locker2){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker1){System.out.println(t2两把锁加锁成功);}}});t1.start();t2.start();} }上面代码就会出现死锁问题。每个线程都卡在了第二次加锁的过程。 如果是一个服务器程序,出现死锁。死锁的线程就僵住了就无法继续工作了 会对程序造成严重的影响。 N 个线程 M 把锁哲学家就餐问题 每个哲学家,主要要做两件事 1.思考人生.会放下筷子 2.吃面.会拿起左手和右手的筷子,再去夹面条吃。 其他设定: 1.每个哲学家,啥时候思考人生,啥时候吃面条,都不确定的 2.每个哲学家一旦想吃面条了就会非常固执的完成吃面条的操作。如果此时他的筷子被别人使用了就会阻塞等待,而且等待过程中不会放下手里已经拿着的筷子。 基于上述的模型设定,绝大部分情况下这些哲学家都是可以很好的工作的。但是,如果出现了极端情况就会出现死锁。比如同一时刻五个哲学家都想吃面并且同时伸出 左手 拿起左边的筷子。再尝试伸右手拿右边的筷子。此时就会哪个哲学家都不会吃上面条了这里五个哲学家无根筷子相当于5个线程5把锁。 避免死锁 死锁产生的必要条件 互斥使用一个线程获取到一把锁之后别的线程不能获取到这个锁 实际使用的锁,一般都是互斥的(锁的基本特性) 不可抢占锁: 只能是被持有者主动释放而不能是被其他线程直接抢走 也是锁的基本的特性 请求和保持: 一个线程去尝试获取多把锁在请求获取第二把锁的过程中会保持对第一把锁的获取状态。 取决于代码结构(很可能会影响到需求) 循环等待: t1 尝试获取 locker2需要 等待 t2 执行完释放 locker2。t2 尝试获取 locker1需要 等待 t1 执行完释放 locker1。 取决于代码结构 缺一不可只要能够破坏其中的任意一个条件,都可以避免出现死锁。 解决死锁问题的最关键要点破除循环等待。 破除循环等待针对锁进行编号。并且规定加锁的顺序。比如,约定,每个线程如果要获取多把锁必须先获取 编号小的锁后获取编号大的锁。只要所有线程加锁的顺序都严格遵守上述顺序就一定不会出现环等待。 针对上面死锁代码进行加锁编号来解决死锁问题 public class ThreadDemo {private static Object locker1 new Object();private static Object locker2 new Object();public static void main(String[] args) {Thread t1 new Thread(()-{synchronized (locker1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println(t1两把锁加锁成功);}}});Thread t2 new Thread(()-{synchronized (locker1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println(t2两把锁加锁成功);}}});t1.start();t2.start();} }Synchronized 原理 Synchronized的锁策略 synchronized 具体是采用了哪些锁策略呢? 1.synchronized 既是悲观锁, 也是乐观锁2.synchronized 既是重量级锁,也是轻量级锁.(自适应)3.synchronized 重量级锁部分是基于系统的互斥锁实现的; 轻量级锁部分是基于自旋锁实现的4.synchronized 是非公平锁(不会遵守先来后到 锁释放之后,哪个线程拿到锁,各凭本事)5.synchronized 是可重入锁.(内部会记录哪个线程拿到了锁,记录引用计数)6.synchronized 不是读写锁,是互斥锁。 synchronized 加锁过程 代码中写了一个 synchronized 之后这里可能会产生一系列的“自适应的过程”锁升级(锁膨胀)。 无锁 - 偏向锁 - 轻量级锁 - 重量级锁 偏向锁不是真的加锁而只是做了一个”标记“。如果有别的线程来竞争锁了才会真的加锁。如果没有别的线程竞争就自始至终都不会真的加锁了。**加锁本身,有一定开销。能不加就不加。非得是有人来竞争了才会真的加锁。**偏向锁在没有其他人竞争的时候就仅仅是一个简单的标记(非常轻量)。一旦有别的线程尝试进行加锁就会立即把偏向锁,升级成真正加锁的状态让别人只能阻塞等待。 轻量级锁sychronized 通过自旋锁的方式来实现轻量级锁。我这边把锁占据了,另一个线程就会按照自旋的方式来反复查询当前的锁的状态是不是被释放了。但是,后续如果竞争这把锁的线程越来越多了(锁冲突更激烈了)从轻量级锁升级成重量级锁。 轻量级锁的操作是比较消耗 CPU 的。 如果能够比较快速的拿到锁多消耗点 CPU 也不亏。但是随着竞争更加激烈即使前一个线程释放锁 ,也不一定能拿到锁啥时候能拿到时间可能会比较久了。 synchronized 的优化操作 锁消除编译器,会智能的判定,当前这个代码是否有必要加锁。如果你写了加锁,但是实际上没有必要加锁就会把加锁操作自动删除掉。 锁粗化关于锁的粒度如果加锁操作里包含的实际要执行的代码越多,就认为锁的粒度越粗。一段逻辑中如果出现多次加锁解锁, 编译器 JVM 会自动进行锁的粗化 。 有的时候,希望锁的粒度小比较好,并发程度更高。有的时候,也希望锁的粒度大比较好因为加锁解锁本身也有开销。 CAS CAS的概念 CAS: 全称Compare and swap字面意思:”比较并交换“。能够比较和交换 某个寄存器 中的值 和 内存 中的值看是否相等。如果相等则把另外一个寄存器中的值和内存进行交换。 boolean CAS(address, expectValue, swapValue) {if (address expectedValue) {address swapValue;//此处严格的说,是把 address 内存的值,和 swapValue 寄存器里的值, 进行交换。//但是一般我们重点关注的是内存中的值。//寄存器往往作为保存临时数据的方式这里的值是啥很多时候就忽略了。return true;}return false; }address内存地址 expectValue, swapValue寄存器中的值 上面一段逻辑是通过一条 cpu 指令完成的(原子的)。这个就给我们编写线程安全代码,打开了新世界的大门。基于 CAS 又能衍生出一套无锁编程“。但是CAS 的使用范围具有一定局限性的。 CAS的实现是硬件予以了支持软件层面才能做到 。 CAS的应用 1. 实现原子类 比如,多线程针对一个 count 变量进行 在java 标准库中基于CAS,已经提供了一组原子类。 标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的。AtomicBooleanAtomicIntegerAtomicIntegerArrayAtomicLongAtomicReferenceAtomicStampedReference 以 AtomicInteger 举例常见方法有 addAndGet(int delta); 相当于 i delta; getAndIncrement 相当于 i 操作。 incrementAndGet 相当于 i 操作。 getAndDecrement 相当于 i-- 操作。 decrementAndGet 相当于 --i 操作。 public class ThreadDemo26 {private static AtomicInteger count new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()-{for (int i 0; i 5000; i) {//Java 不像 C Python 能支持运算符重载这里必须通过调用方法的方式来完成自增count.getAndIncrement();}});Thread t2 new Thread(()-{for (int i 0; i 5000; i) {count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count.get());} }上述的原子类,就是基于 CAS 来实现的。 伪代码实现 class AtomicInteger {private int value;//很可能有个别的线程穿插在这俩代码之间,把 value 给改.public int getAndIncrement() {int oldValue value;while ( CAS(value, oldValue, oldValue1) ! true) {oldValue value;}return oldValue;} }oldValue也可以是寄存器中的值由于以往学过的 C/Java 里头,并没有啥办法定义一个“寄存器”的变量。 这里的比较value和oldValue相等其实就是在检查当前 value 是不是变了。是不是被别的线程穿插进来做出修改了。进一步就发现了当前的 操作不是一气呵成的原子操作了。一旦发现出现其他线程穿插的情况立即重新读取内存的值准备下一次尝试。 加锁保证线程安全: 通过锁,强制避免出现穿插。 原子类/CAS 保证线程安全: 借助 CAS 来识别当前是否出现其他线程穿插”的情况。如果没穿插,此时直接修改 就是安全的。如果出现穿插了就重新读取内存中的最新的值,再次尝试修改。 2. 实现自旋锁 基于 CAS 实现更灵活的锁, 获取到更多的控制权。 自旋锁伪代码 public class SpinLock {private Thread owner null;//此处使用 owner 表示当前是哪个线程持有的这把锁.null 解锁状态public void lock(){// 通过 CAS 看当前锁是否被某个线程持有.// 如果这个锁已经被别的线程持有, 那么就自旋等待.// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.while(!CAS(this.owner, null, Thread.currentThread())){//Thread.currentThread()获取当前线程引用//哪个线程调用 lock,这里得到的结果就是哪个线程的引用}//当该锁已经处于加锁状态,这里就会返回 false, //CAS 不会进行实际的交换操作.接下来循环条件成立,继续进入下一轮循环}public void unlock (){this.owner null;} }CAS 的 ABA 问题 CAS 关键要点是比较 寄存器1 和 内存 的值。通过这里的是否相等来判定 内存的值 是否发生了改变。如果内存的值变了存在其他线程进行了修改。如果内存的值没变没有别的线程修改,接下来进行的修改就是安全的。 ABA 的问题: 另一个线程把 变量的值从 A - B,又从 B - A。此时本线程区分不了这个值是始终没变还是出现变化又回来了的情况。 大部分情况下,就算是出现 ABA 问题也没啥太大影响。但是如果遇到一些极端的场景可能会出现问题 账户 100 希望取款50还剩50。假设出现极端问题按第一下取款的时候,卡了一下, 我又按了一下。产生了两个“取款”请求,ATM 使用两个线程来处理这俩请求。假设按照 CAS 的方式进行取款每个线程这样操作 读取账户余额.放到变量 M 中。使用 CAS 判定当前实际余额是否还是 M。如果是就把实际余额修改成 M-50。如果不是就放弃当前操作(操作失败)。 上面这个ABA问题属于非常巧合的情况取款的时候卡了 碰巧这个时候有人给你转了50 虽然上述操作,概率比较小,也需要去考虑。ABA问题的解决方式 ABA 问题,CAS 基本的思路是 没有问题 的,但是主要是修改操作能够进行反复改变,就容易让咱们 cas 的判定失效。CAS 判定的是“值相同”实际上期望的是“值没有变化过。比如约定,值只能单向变化(比如只能增长,不能减小)。虽余额不能只增张不减少但是衡量余额是否改变的标准可以是看版本号。给账户余额安排一个 其他属性版本号(只增加,不减少)。使用 CAS 判定版本号如果版本号相同则数据一定是没有修改过的如果数据修改过版本号一定要增加。 JUC(java.util.concurrent) 的常见类 juc中的类是为了并发编程准备的。java官方文档 Callable interface 也是一种创建线程的方式 Runnable 能表示一个任务 (run 方法)返回 void Callable 也能表示一个任务 (call 方法)返回一个具体的值,类型可以通过泛型参数来指定(Object)。 如果进行多线程操作,如果你只是关心多线程执行的过程使用 Runnable 即可如。果是关心多线程的计算结果使用 Callable 更合适。 通过多线程的方式计算一个公式比如创建一个线程,让这个线程计算 1 2 3 … 1000使用Callable解决更合适。 使用 Callable 不能直接作为 Thread 的构造方法参数借助FutureTask 来作为Thread的构造方法参数 public class ThreadDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {CallableInteger callable new CallableInteger() {Overridepublic Integer call() throws Exception {int sum 0;for (int i 1; i 1000; i) {sum i;}return sum;}};//使用 Callable 不能直接作为 Thread 的构造方法参数FutureTaskInteger futureTask new FutureTask(callable);Thread t new Thread(futureTask);t.start();//获取 call 方法的返结果get ,类似于join 一样, 如果 call 方法没执行完,会阻塞等待Integer result futureTask.get();System.out.println(result);} }ReentrantLock 可重入锁这个锁 没有 synchronized 那么常用,但是也是一个可选的加锁的组件。这个锁在使用上更接近于 C 里的锁。 lock() 加锁unlock() 解锁 分开操作就容易出现unlock 调用不到的情况容易遗漏。比如,中间 return / 抛出异常了。ReentrantLock 具有一些特点,是 synchronized 不具备的功能(优势) 提供了一个 tryLock 方法进行加锁 对于 lock 操作,如果加锁不成功,就会阻塞等待(死等)对于 tryLock如果加锁失败直接返回 false/也可以设定等待时间。tryLock 给加锁操作提供了更多的可操作空间。 ReentrantLock 有两种模式。可以工作在公平锁状态下也可以工作在非公平锁的状态下。 构造方法中通过参数设定的 公平/非公平模式。 ReentrantLock 也有等待通知机制搭配 Condition 这样的类来完成这里的等待通知。要比 wait notify 功能更强 虽然ReentrantLock有上述这些优点但是 ReentrantLock 劣势也很明显(比较致命)unlock 容易遗漏使用 finally 来执行 unlock。 synchronized 锁对象是任意对象。ReentrantLock 锁对象就是自己本身。如果你多个线程针对不同的 ReentrantLock 调用 lock 方法此时是不会产生锁竞争的。实际开发中,进行多线程开发用到锁还是首选 synchronized。 原子类 原子类的应用场景: 计数请求播放量点赞量,投币量,转发量,收藏量。同一个视频,有很多人都在同时的播放/点赞/收藏 统计效果 统计出现错误的请求数目。— 使用原子类,记录出错的请求的数目。— 另外写一个监控服务器获取到线上服务器的这些错误计数并且以曲线图的方式绘制到页面上。 某次发布程序之后,发现,突然这里的错误数大幅度上升,说明你这个新版本代码大概率存在 bug。 统计收到的请求总数(衡量服务器的压力)。统计每个请求的响应时间 平均的响应时间(衡量服务器的运行效率)。 最低 1% 的响应时间是多少(1% low 帧)。线上服务器通过这些统计内容,进行简单计数 实现监控服务器,获取/统计/展示/报警。 信号量 Semaphore Semaphore 是并发编程中的一个重要的概念/组件。准确来说Semaphore 是一个计数器(变量)描述了**可用资源的个数**。描述的是当前这个线程,是否**“有临界资源可以用“**。 P 操作申请了一个可用资源 - 1。accquire (申请)V 操作释放了一个可用资源 1。release (释放) 当计数器数值为 0 的时候继续进行 P 操作就会阻塞等待,一直等待到其他线程执行了 V 操作释放了一个空闲 资源为止。锁,本质上是一个特殊的信号量(里面的数值,非 0 即 1二元信号量)。信号量要比锁更广义,不仅仅可以描述一个资源,还可以描述 N 个资源。虽然概念上更广泛,实际开发中,还是锁更多一些(二元信号量的场景是更常见的)。 //信号量 public class ThreadDemo {public static void main(String[] args) throws InterruptedException {// 构造方法中, 就可以用来指定计数器的初始值.Semaphore semaphore new Semaphore(4);semaphore.acquire();//计数器-1System.out.println(执行p操作);semaphore.acquire();//计数器-1System.out.println(执行p操作);semaphore.acquire();//计数器-1System.out.println(执行p操作);semaphore.acquire();//计数器-1System.out.println(执行p操作);semaphore.acquire();//计数器-1System.out.println(执行p操作);} }CountDownLatch 针对特定场景一个组件。同时等待 N 个任务执行结束。 下载某个东西有的时候下载一个比较大的文件,比较慢(慢不是因为你家里的网速限制往往是人家服务器这边的限制)。有一些多线程下载器”把一个大的文件拆分成多个小的部分使用多个线程分别下载。每个线程负责下载一部分,每个线程分别是一个网络连接。就会大幅度提高下载速度。假设,分成 10个线程,10个部分来下载。 10个部分都下载完了整体才算完成。 //CountDownLatch public class ThreadDemo {public static void main(String[] args) throws InterruptedException {// 构造方法中, 指定创建几个任务.CountDownLatch countDownLatch new CountDownLatch(10);for (int i 0; i 10; i) {int id i;Thread t new Thread(()- {System.out.println(线程 id 开始工作);try {// 使用 sleep 代指某些耗时操作, 比如下载.Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(线程 id 结束工作);// 每个任务执行结束这里, 调用一下方法// 把 10 个线程想象成短跑比赛的 10 个运动员. countDown 就是运动员撞线了.countDownLatch.countDown();});t.start();}// 主线程如何知道上述所有的任务都完成了呢??// 难道要在主线程中调用 10 次 join 嘛?// 万一要是任务结束, 但是线程不需要结束, join 不就也不行了嘛。// 主线程中可以使用 countDownLatch 负责等待任务结束.// a all 等待所有任务结束. 当调用 countDown 次数 初始设置的次数, await 就会阻塞.countDownLatch.await();System.out.println(多个线程的所有任务都执行完毕了!!);} }线程安全的集合类 多个线程同时操作这个集合类,不会会产生问题就是线程安全的。 Vector, Stack, HashTable, 是线程安全的(不建议用), 其他的集合类不是线程安全的. 在关键的方法中,使用了 synchronized。Vector 和 HashTable 属于是 Java 上古时期搞出来的集合类。加了锁,不一定就线程安全。不加锁也不一定就线程不安全 要具体问题具体分析。 虽然 get 和 set 方法加了 synchronized 但是如果不能正确使用,也可能会出现线程安全问题 如果是多个线程,并发执行 set 操作,由于 synchronized 限制,是线程安全。如果多个线程进行一些更复杂的操作,比如判定 get 的值是 xxx再进行 set可能会线程不安全。 即使把这里的 get 和 set 分别进行加锁。如果不能正确的使用,也可能产生线程安全问题。考虑到实际的逻辑中哪些代码是要作为一个整体的(原子的)。 线程安全下使用ArrayList Collections.synchronizedList(new ArrayList); ArrayList 本身没有使用 synchronized。但是你又不想自己加锁就可以使用上面这个东西相当于让 ArrayList 像 Vector 一样工作。很少会用 使用 CopyOnWriteArrayList 写时复制 多个线程同时修改同一个变量如果多个线程修改不同变量就会安全了。 如果多线程去读取本身就不会有任何线程安全问。一旦有线程修改就会把自身复制一份。尤其是修改比较耗时的话其他线程还是旧的数据上读取。一旦修改完成使用新的 ArrayList 替换目的 ArrayList (本质上就是一个引用的重新赋值速度极快并且又是原子的) 这个过程中,没有引入任何的加锁操作。使用了创建副本 修改副本 使用副本替换。 线程安全下使用HashMap ConcurrentHashMap 线程安全的 hash 表。 HashTable 是在方法上直接加上 synchronized,就相当于针对 this 加锁。 如果两个修改操作是针对两个不同的链表进行修改不会存在线程安全问题。既然这里没有线程安全问题但是锁又不能完全不加因为两个修改可能在同一个链表中同一个位置进行插入操作。 为了解决上面的问题给每个链表都加一把锁。 一个hash表上面的链表个数这么多两个线程正好在同时操作同一个链表的概率本身就是比较低的整体锁的开销就大大降低了。由于 synchronized 随便拿个对象都可以用来加锁就可以简单的使用每个链表的头结点,作为锁对象即可。 ConcurrentHashMap 改进 [核心] 减小了锁的粒度,每个链表有一把锁。大部分情况下都不会涉及到锁冲突。广泛使用了 CAS 操作(比如size)写操作进行了加锁(链表级),读操作,不加锁了。针对扩容操作进行了优化浙进式扩容。 HashTable 一旦触发扩容, 就会立即的一口气的完成所有元素的搬运这个过程相当耗时。大部分请求都很顺畅突然某个请求就卡了比较久。化整为零当需要进行扩容的时候会创建出另一个更大的数组然后把旧的数组上的数据逐渐的往新的数组上搬运。会出现一段时间旧数组和新数组同时存在。 新增元素,往新数组上插入。删除元素,把旧数组的元素给删掉即可。查找元素,新数组旧数组都得查找。修改元素,统一把这个元素给搞到新数组上。 与此同时每个操作都会触发一定程度搬运。每次搬运一点就可以保证整体的时间不是很长。积少成多之后,逐渐完成搬运了也就可以把之前的旧数组彻底销毁了。 介绍下 ConcurrentHashMap的锁分段技术? Java 8 之前,ConcurrentHashMap 是使用分段锁从 Java 8 开始,就是每个链表自己一把锁了。
http://www.w-s-a.com/news/902972/

相关文章:

  • 网站建设维护工作经验深圳定制展会
  • 新闻类网站备案WordPress评论昵称显示错误
  • 如何建立一个个人网站自己做一个购物网站
  • 吴忠网站建设公司中国建筑股份有限公司 官网
  • 深圳电商网站开发公司page list wordpress
  • 长安外贸网站建设顺德区网站设计建设企业
  • 临沂市建设局网站简介专业建设网站开发
  • 肇庆网站制作设计中国企业500强招聘
  • 苏州厂房装修宁波seo网络推广外包报价
  • 文山知名网站建设惠州哪家做网站好
  • 物流网站风格网站登录密码保存在哪里设置
  • 免费网站怎么建立icodepython基础教程
  • 无障碍网站建设方案wordpress 任务管理系统
  • iis5.1发布网站中小企业网络营销存在的问题研究论文
  • 阳泉软件定制网站建设网站可以做多语言的吗
  • 建设网站的目的及功能定位主要包括哪些内容百度关键词优化
  • 开一个小程序要多少钱宁波seo网络推广外包报价
  • 网站备案最新备案号电子商务网站建设的规章制度
  • wordpress制作单页网站导航页面鞍山信息港招聘信息
  • 屏蔽ip地址访问网站自己做衣服的网站
  • 网站建设 域名业务 邮箱哪里有网站建设中心
  • 免费网站赚钱重庆建设摩托车股份有限公司
  • 合肥水运建设工程监理网站自己买服务器能在wordpress建网站
  • wordpress积分商城主题整站seo排名要多少钱
  • 鲜花网站建设的利息分析网站设计与制作专业
  • 深圳网站建设排名做网站的公司高创
  • 杭州哪家做外贸网站全国物流网站有哪些平台
  • 企业网站建设个人博客鞍山晟宇网站建设
  • 广东省自然资源厅网站h5移动端网站模板下载
  • 网站建设和安全管理制度云南九泰建设工程有限公司官方网站