Java多线程之并发编程的基石CAS机制详解

目录
  • 一、CAS机制简介
    • 1.1、悲观锁和乐观锁更新数据方式
    • 1.2、什么是CAS机制
    • 1.3、CAS与sychronized比较
    • 1.4、Java中都有哪些地方应用到了CAS机制呢?
    • 1.5、CAS 实现自旋锁
    • 1.6、CAS机制优缺点
      • 1>ABA问题
      • 2>可能会消耗较高的CPU
      • 3>不能保证代码块的原子性
  • 二、Java提供的CAS操作类--Unsafe类
    • 2.1、Unsafe类简介
    • 2.2、Unsafe类的使用
  • 三、CAS使用场景
    • 3.1、使用一个变量统计网站的访问量
    • 3.2、现在我们尝试在代码中引入AtomicInteger类
  • 四、Java中的原子操作类
    • 参考链接:
  • 总结

前言: synchronized保证了线程安全,但是在某些情况下,却不是一个最优选择,关键在于性能问题。Java中提供了很多原子操作类来保证共享变量操作的原子性。这些原子操作的底层原理都是使用了CAS机制。既然用锁或 synchronized 关键字可以实现原子操作,那么为什么还要用 CAS 呢,因为加锁或使用 synchronized 关键字带来的性能损耗较大,而用 CAS 可以实现乐观锁,它实际上是直接利用了 CPU 层面的指令,没有加锁和线程上下文切换的开销,所以性能很高。

一、CAS机制简介

1.1、悲观锁和乐观锁更新数据方式

CAS机制是一种数据更新的方式。在具体讲什么是CAS机制之前,我们先来聊下在多线程环境下,对共享变量进行数据更新的两种模式:悲观锁模式和乐观锁模式。

悲观锁更新的方式认为:在更新数据的时候大概率会有其他线程去争夺共享资源,所以悲观锁的做法是:第一个获取资源的线程会将资源锁定起来,其他没争夺到资源的线程只能进入阻塞队列,等第一个获取资源的线程释放锁之后,这些线程才能有机会重新争夺资源。synchronized就是Java中悲观锁的典型实现,synchronized使用起来非常简单方便,但是会使没争抢到资源的线程进入阻塞状态,线程在阻塞状态和Runnable状态之间切换效率较低(比较慢)。比如你的更新操作其实是非常快的,这种情况下你还用synchronized将其他线程都锁住了,线程从Blocked状态切换回Runnable华的时间可能比你的更新操作的时间还要长。

乐观锁更新方式认为:在更新数据的时候其他线程争抢这个共享变量的概率非常小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。CAS机制就是乐观锁的典型实现。

1.2、什么是CAS机制

CAS,是Compare and Swap的简称,是一种用于在多线程环境下实现同步功能的机制。CAS 操作包含三个操作数 -- 内存位置、预期数值和新值。CAS 的实现逻辑是将内存位置处的数值与预期数值相比较,若相等,则将内存位置处的值替换为新值。若不相等,则不做任何操作。

在 Java 中,Java 并没有直接实现 CAS,CAS 相关的实现是通过 C++ 内联汇编的形式实现的。Java 代码需通过 JNI 才能调用。

CAS这个机制中有三个核心的参数:

主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)

工作内存中共享变量的副本值,也叫预期值:A

需要将共享变量更新到的最新值:B

如上图中,主存中保存V值,线程中要使用V值要先从主存中读取V值到线程的工作内存A中,然后计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A,并重新计算得到B,如果相同,则将B值赋给V。

值得注意的是CAS机制中的这步步骤是原子性的(从指令层面提供的原子操作),所以CAS机制可以解决多线程并发编程对共享变量读写的原子性问题。

1.3、CAS与sychronized比较

从思想上来说:

①. synchronized属于【悲观锁】

悲观锁认为:程序中的【并发】情况严重,所以【严防死守】

②. CAS属于【乐观锁】

乐观锁认为:程序中的【并发】情况不那么严重,所以让【线程不断去尝试更新】

这2种机制没有绝对的好与坏,关键看使用场景。在并发量非常高的情况下,反而用同步锁更合适一些。

1.4、Java中都有哪些地方应用到了CAS机制呢?

a、Atomic系列类

b、Lock系列类底层实现

c、Java1.6以上版本,synchronized转变为重量级锁之前,也会采用CAS机制

1.5、CAS 实现自旋锁

既然用锁或 synchronized 关键字可以实现原子操作,那么为什么还要用 CAS 呢,因为加锁或使用 synchronized 关键字带来的性能损耗较大,而用 CAS 可以实现乐观锁,它实际上是直接利用了 CPU 层面的指令,没有加锁和线程上下文切换的开销,所以性能很高。

上面也说了,CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转,翻译成人话就是循环,一般是用一个无限循环实现。这样一来,一个无限循环中,执行一个 CAS 操作,当操作成功,返回 true 时,循环结束;当返回 false 时,接着执行循环,继续尝试 CAS 操作,直到返回 true。

其实 JDK 中有好多地方用到了 CAS ,尤其是 java.util.concurrent包下,比如 CountDownLatch、Semaphore、ReentrantLock 中,再比如 java.util.concurrent.atomic 包下,相信大家都用到过 Atomic* ,比如 AtomicBoolean、AtomicInteger 等。

1.6、CAS机制优缺点

CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

CAS机制CAS机制缺点

1>ABA问题

ABA问题:CAS在操作的时候会检查变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是A,接着变成B,最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,一个版本号和某个值,A——>B——>A问题就变成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference类解决ABA问题,用Pair这个内部类实现,包含两个属性,分别代表版本号和引用,在compareAndSet中先对当前引用进行检查,再对版本号标志进行检查,只有全部相等才更新值。

AtomicStampedReferenceAtomicMarkableReference就是用来解决CAS中的ABA问题的。他们解决ABA问题的原理类似,都是通过一个版本号来区分有没被更新过。

AtomicStampedReference:带版本戳的原子引用类型,版本戳为int类型。

AtomicMarkableReference:带版本戳的原子引用类型,版本戳为boolean类型。

2>可能会消耗较高的CPU

看起来CAS比锁的效率高,从阻塞机制变成了非阻塞机制,减少了线程之间等待的时间。每个方法不能绝对的比另一个好,在线程之间竞争程度大的时候,如果使用CAS,每次都有很多的线程在竞争,也就是说CAS机制不能更新成功。这种情况下CAS机制会一直重试,这样就会比较耗费CPU。因此可以看出,如果线程之间竞争程度小,使用CAS是一个很好的选择;但是如果竞争很大,使用锁可能是个更好的选择。在并发量非常高的环境中,如果仍然想通过原子类来更新的话,可以使用AtomicLong的替代类:LongAdder。

3>不能保证代码块的原子性

Java中的CAS机制只能保证一个共享变量的原子操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

CAS机制优点

可以保证变量操作的原子性;

并发量不是很高的情况下,使用CAS机制比使用锁机制效率更高;

在线程对共享资源占用时间较短的情况下,使用CAS机制效率也会较高。

二、Java提供的CAS操作类--Unsafe类

2.1、Unsafe类简介

在研究JDK中AQS时,会发现这个类很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,Java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面,肯定要通过用C++编写的native本地方法来扩展实现。JDK提供了一个类来满足CAS的要求,sun.misc.Unsafe,从名字上可以大概知道它用于执行低级别、不安全的操作,AQS就是使用此类完成硬件级别的原子操作。UnSafe通过JNI调用本地C++代码,C++代码调用CPU硬件指令集。

Unsafe是一个很强大的类,它可以分配内存、释放内存、可以定位对象某字段的位置、可以修改对象的字段值、可以使线程挂起、使线程恢复、可进行硬件级别原子的CAS操作等等。

从Java5开始引入了对CAS机制的底层的支持,在这之前需要开发人员编写相关的代码才可以实现CAS。在原子变量类Atomic中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代码,在这里的代码都是调用了底层(核心代码调用native修饰的方法)的实现方法。

在AtomicInteger源码中可以看getAndSet方法和compareAndSet方法之间的关系,compareAndSet方法调用了底层的实现,该方法可以实现与一个volatile变量的读取和写入相同的效果。在前面说到了volatile不支持例如i++这样的复合操作,在Atomic中提供了实现该操作的方法。JVM对CAS的支持通过这些原子类(Atomic***)暴露出来,供我们使用。

而Atomic系类的类底层调用的是Unsafe类的API,Unsafe类提供了一系列的compareAndSwap*方法,下面就简单介绍下Unsafe类的API:

long objectFieldOffset(Field field)方法:返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该Unsafe函数中访问指定字段时使用。如下代码使用Unsafe类获取变量value在AtomicLong对象中的内存偏移。

static {
   try {
       valueOffset = unsafe.objectFieldOffset
           (AtomicInteger.class.getDeclaredField("value"));
   } catch (Exception ex) { throw new Error(ex); }
}
  • int arrayBaseOffset(Class arrayClass)方法:获取数组中第一个元素的地址。
  • int arrayIndexScale(Class arrayClass)方法:获取数组中一个元素占用的字节。
  • boolean compareAndSwapLong(Object obj, long offset, long expect, long update)方法:比较对象obj中偏移量为offset的变量的值是否与expect相等,相等则使用update值更新,然后返回true,否则返回false,这次处理器提供的一个原子性指令。
  • public native long getLongvolatile(Object obj, long offset)方法:获取对象obj中偏移量为offset的变量对应volatile语义的值。
  • void putLongvolatile(Object obj, long offset, long value)方法:设置obj对象中offset偏移的类型为long的field的值为value,支持volatile语义。
  • void putOrderedLong(Object obj, long offset, long value)方法:设置obj对象中offset偏移地址对应的long型field的值为value。这是一个有延迟的putLongvolatile方法,并且不保证值修改对其他线程立刻可见。只有在变量使用volatile修饰并且预计会被意外修改时才使用该方法。
  • void park(boolean isAbsolute, long time)方法:阻塞当前线程,其中参数isAbsolute等于false且time等于0表示一直阻塞。time大于0表示等待指定的time后阻塞线程会被唤醒,这个time是个相对值,是个增量值,也就是相对当前时间累加time后当前线程就会被唤醒。如果isAbsolute等于true,并且time大于0,则表示阻塞的线程到指定的时间点后会被唤醒,这里time是个绝对时间,是将某个时间点换算为ms后的值。另外,当其他线程调用了当前阻塞线程的interrupt方法而中断了当前线程时,当前线程也会返回,而当其他线程调用了unPark方法并且把当前线程作为参数时当前线程也会返回。
  • void unpark(Object thread)方法:唤醒调用park后阻塞的线程。

下面是JDK8新增的函数,这里只列出Long类型操作。

long getAndSetLong(Object obj, long offset, long update)方法:获取对象obj中偏移量为offset的变量volatile语义的当前值,并设置变量volatile语义的值为update。

//这个方法只是封装了compareAndSwapLong的使用,不需要自己写重试机制
public final long getAndSetLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var4));
    return var6;
}

long getAndAddLong(Object obj, long offset, long addValue)方法:获取对象obj中偏移量为offset的变量volatile语义的当前值,并设置变量值为原始值+addValue,原理和上面的方法类似。

2.2、Unsafe类的使用

三、CAS使用场景

  • 使用一个变量统计网站的访问量;
  • Atomic类操作;
  • 数据库乐观锁更新。

3.1、使用一个变量统计网站的访问量

要实现一个网站访问量的计数器,可以通过一个Long类型的对象,并加上synchronized内置锁的方式。但是这种方式使得多线程的访问变成了串行的,同一时刻只能有一个线程可以更改long的值,那么为了能够使多线程并发的更新long的值,我们可以使用J.U.C包中的Atomic原子类。这些类的更新是原子的,不需要加锁即可实现并发的更新,并且是线程安全的。

可是Atomic原子类是怎么保证并发更新的线程安全的呢?让我们看一下AtomicLong的自增方法incrementAndGet():

public final long incrementAndGet() {
    // 无限循环,即自旋
    for (;;) {
        // 获取主内存中的最新值
        long current = get();
        long next = current + 1;
        // 通过CAS原子更新,若能成功则返回,否则继续自旋
        if (compareAndSet(current, next))
            return next;
    }
}
private volatile long value;
public final long get() {
    return value;
}

可以发现其内部保持着一个volatile修饰的long变量,volatile保证了long的值更新后,其他线程能立即获得最新的值。
在incrementAndGet中首先是一个无限循环(自旋),然后获取long的最新值,将long加1,然后通过compareAndSet()方法尝试将long的值有current更新为next。如果能更新成功,则说明当前还没有其他线程更新该值,则返回next,如果更新失败,则说明有其他线程提前更新了该值,则当前线程继续自旋尝试更新。

简单总结

总体来说,AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference原理比较简单:使用CAS保证原子性,使用volatile保证可见性,最终能保证共享变量操作的线程安全。

AtomicLongArray、AtomicIntArray和AtomicReferenceArray的实现原理略有不同,是用CAS机制配合final机制来实现共享变量操作的线程安全的。感兴趣的同学可以自己分析下,也是比较简单的。

CAS的操作其底层是通过调用sun.misc.Unsafe类中的CompareAndSwap的方法保证线程安全的。Unsafe类中主要有下面三种CompareAndSwap方法:

public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);

可以看到这些方法都是native的,需要调用JNI接口,也即通过操作系统来保证这些方法的执行。

3.2、现在我们尝试在代码中引入AtomicInteger类

在使用Integer的时候,必须加上synchronized保证不会出现并发线程同时访问的情况

public class AtomicInteger {
    private static Integer count =0;
    public static void main(String[] args) {
        //开启两个线程
        for(int i=0;i<2;i++)
        {
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    //每个线程当中让count自增1000次
                    for(int j=0;j<1000;j++)
                    {
                        increment();
                    }
                }
            }).start();
        }
        //让主线程睡2秒,避免直接打印count值为0
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count="+count);
    }
    //加上synchronized保证不会出现并发线程同时访问的情况,否则结果可能只有1千多
     public synchronized static void increment() {
        count++;
    }
}

而在AtomicInteger中却不用加上synchronized,在这里AtomicInteger是提供原子操作的

在某些情况下,原子类代码的性能会比Synchronized更好,因为没有加锁的线程同步上下文切换开销,底层采用了CAS机制保证共享变量原子性,还配合volatile保证内存可见性,最终能保证共享变量操作的线程安全。

四、Java中的原子操作类

在JDK1.5版本之前,多行代码的原子性主要通过synchronized关键字进行保证。在JDK1.5版本,Java提供了原子类型专门确保变量操作的原子性。所谓的原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如:AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。

为了方面对这些类逐级掌握,我将这些原子类型分为以下几类:

  • 普通原子类型:提供对boolean、int、long和对象的原子性操作。

    • AtomicBoolean
    • AtomicInteger
    • AtomicLong
    • AtomicReference
  • 原子类型数组:提供对数组元素的原子性操作。
    • AtomicLongArray
    • AtomicIntegerArray
    • AtomicReferenceArray
  • 原子类型字段更新器:提供对指定对象的指定字段进行原子性操作。
    • AtomicLongFieldUpdater
    • AtomicIntegerFieldUpdater
    • AtomicReferenceFieldUpdater
  • 带版本号的原子引用类型:以版本戳的方式解决原子类型的ABA问题。
    • AtomicStampedReference
    • AtomicMarkableReference
  • 原子累加器(JDK1.8):AtomicLong和AtomicDouble的升级类型,专门用于数据统计,性能更高。
    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

原子类型累加器是JDK1.8引进的并发新技术,它可以看做AtomicLong和AtomicDouble的部分加强类型。低并发、一般的业务场景下AtomicLong是足够了。如果并发量很多,存在大量写多读少的情况,那LongAdder可能更合适,代价是消耗更多的内存空间。

AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。但是,高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时AtomicLong的自旋会成为瓶颈。

这就是LongAdder引入的初衷——解决高并发环境下AtomicLong的自旋瓶颈问题。

LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。这种做法有没有似曾相识的感觉?没错,
ConcurrentHashMap中的“分段锁”其实就是类似的思路。

参考链接:

并发编程的基石——CAS机制

深入理解CAS机制

Atomic系列类整体介绍

原子类型累加载器

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • java并发编程之cas详解

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值.这听起来可能有一点复杂但是实际上你理解之后发现很简单,接下来,让我们跟深入的了解一下这项技术. CAS的使用场景 在程序和算法中一个经常出现的模式就是"check and act"模式.先检查后操作模式发生在代码中首先检查一个变量的值,然后再基于这个值做一些操作.下面是一个

  • Java CAS机制的一些理解

    多线程实践 public class test { private static int x; public static void main(String[] args) throws InterruptedException { Thread task1 = new Thread(){ @Override public void run() { super.run(); for (int i=0; i<1000; i++){ x=x+1; } } }; Thread task2 = new

  • 浅谈Java非阻塞同步机制和CAS

    什么是非阻塞同步 非阻塞同步的意思是多个线程在竞争相同的数据时候不会发生阻塞,从而能够在更加细粒度的维度上进行协调,从而极大的减少线程调度的开销,从而提升效率.非阻塞算法不存在锁的机制也就不存在死锁的问题. 在基于锁的算法中,如果一个线程持有了锁,那么其他的线程将无法进行下去.使用锁虽然可以保证对资源的一致性访问,但是在挂起和恢复线程的执行过程中存在非常大的开销,如果锁上面存在着大量的竞争,那么有可能调度开销比实际工作开销还要高. 悲观锁和乐观锁 我们知道独占锁是一个悲观锁,悲观锁的意思就是假设

  • Java并发编程总结——慎用CAS详解

    一.CAS和synchronized适用场景 1.对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源:而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能. 2.对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized.以java.util.concurrent.atomic包中AtomicInteger类为例,其getAn

  • 详解java 中的CAS与ABA

    1. 独占锁: 属于悲观锁,有共享资源,需要加锁时,会以独占锁的方式导致其它需要获取锁才能执行的线程挂起,等待持有锁的钱程释放锁.传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.Java中synchronized和ReentrantLock等独占锁就是悲观锁的思想. 1.1 乐观锁的操作 多线程并发修改一个值时的实现: public class SimulatedCAS { //加volatile的目的是利用其happens-before原则

  • Java并发的CAS原理与ABA问题的讲解

    CAS原理 在计算机科学中,比较和交换(Compare And Swap)是用于实现多线程同步的原子指令. 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值. 这是作为单个原子操作完成的. 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败. 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成(摘自维基本科) CAS流程 以AtomicIntege

  • 深入分析Java并发编程之CAS

    在Java并发编程的世界里,synchronized 和 Lock 是控制多线程并发环境下对共享资源同步访问的两大手段.其中 Lock 是 JDK 层面的锁机制,是轻量级锁,底层使用大量的自旋+CAS操作实现的. 学习并发推荐<Java并发编程的艺术> 那什么是CAS呢?CAS,compare and swap,即比较并交换,什么是比较并交换呢?在Lock锁的理念中,采用的是一种乐观锁的形式,即多线程去修改共享资源时,不是在修改之前就加锁,而是乐观的认为没有别的线程和自己争锁,就是通过CAS的

  • 关于Java 并发的 CAS

    目录 一.为什么要无锁 二.什么是CAS? 三.Java 中的CAS 四.CAS存在的问题 1.自旋的劣势 2.ABA 问题 3.尝试应用 4.CAS 源码 一.为什么要无锁 我们一想到在多线程下保证安全的方式头一个要拎出来的肯定是锁,不管从硬件.操作系统层面都或多或少在使用锁.锁有什么缺点吗?当然有了,不然 JDK 里为什么出现那么多各式各样的锁,就是因为每一种锁都有其优劣势. 使用锁就需要获得锁.释放锁,CPU 需要通过上下文切换和调度管理来进行这个操作,对于一个 「独占锁」 而言一个线程在

  • Java多线程之并发编程的基石CAS机制详解

    目录 一.CAS机制简介 1.1.悲观锁和乐观锁更新数据方式 1.2.什么是CAS机制 1.3.CAS与sychronized比较 1.4.Java中都有哪些地方应用到了CAS机制呢? 1.5.CAS 实现自旋锁 1.6.CAS机制优缺点 1>ABA问题 2>可能会消耗较高的CPU 3>不能保证代码块的原子性 二.Java提供的CAS操作类--Unsafe类 2.1.Unsafe类简介 2.2.Unsafe类的使用 三.CAS使用场景 3.1.使用一个变量统计网站的访问量 3.2.现在我

  • Python并发编程线程消息通信机制详解

    目录 1 Event事件 2 Condition 3 Queue队列 4 总结一下 前面我已经向大家介绍了,如何使用创建线程,启动线程.相信大家都会有这样一个想法,线程无非就是创建一下,然后再start()下,实在是太简单了. 可是要知道,在真实的项目中,实际场景可要我们举的例子要复杂的多得多,不同线程的执行可能是有顺序的,或者说他们的执行是有条件的,是要受控制的.如果仅仅依靠前面学的那点浅薄的知识,是远远不够的. 那今天,我们就来探讨一下如何控制线程的触发执行. 要实现对多个线程进行控制,其实

  • Java多线程之并发编程的核心AQS详解

    目录 一.AQS简介 1.1.AOS概念 1.2.AQS的核心思想 1.3.AQS是自旋锁 1.4.AQS支持两种资源分享的方式 二.AQS原理 2.1.同步状态的管理 2.2.等待队列 2.3.CLH队列中的结点 2.4.队列定义 2.5.AQS底层的CAS机制 2.6.通过ReentrantLock理解AQS 三.AQS方法 3.1.用户需要自己重写的方法 3.2.AQS 提供的一系列模板方法 3.3.acquire(int)方法 3.4.release(int)方法 3.5.acquire

  • 深入理解Java多线程与并发编程

    一.多线程三大特性 多线程有三大特性:原子性.可见性.有序性. 原子性 (跟数据库的事务特性中的原子性类似,数据库的原子性体现是dml语句执行后需要进行提交): 理解:即一个操作或多个操作,要么全部执行并且执行的过程中不会被任何因素打断,要么都不执行. 一个很经典的例子就是银行账户转账问题: 比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元.这2个操作必须要具备原子性才能保证不出现一些意外的问题. 我们操作数据也是如此,比如i = i+1:其

  • java并发编程StampedLock高性能读写锁详解

    目录 一.读写锁 二.悲观读锁 三.乐观读 一.读写锁 在我的<java并发编程>上一篇文章中为大家介绍了<ReentrantLock读写锁>,ReentrantReadWriteLock可以保证最多同时有一个线程在写数据,或者可以同时有多个线程读数据,但读写不能同时进行. 比如你正在做的是日志,有一个线程正在做写操作,但是在写日志的时候你可能需要把日志集中转移到集中管理日志服务,但是此时读线程不能读数据(因为无法获取读锁).面对这个需求,ReentrantReadWriteLoc

  • Java并发编程(CyclicBarrier)实例详解

    Java并发编程(CyclicBarrier)实例详解 前言: 使用JAVA编写并发程序的时候,我们需要仔细去思考一下并发流程的控制,如何让各个线程之间协作完成某项工作.有时候,我们启动N个线程去做一件事情,只有当这N个线程都达到某一个临界点的时候,我们才能继续下面的工作,就是说如果这N个线程中的某一个线程先到达预先定义好的临界点,它必须等待其他N-1线程也到达这个临界点,接下来的工作才能继续,只要这N个线程中有1个线程没有到达所谓的临界点,其他线程就算抢先到达了临界点,也只能等待,只有所有这N

  • java并发编程专题(五)----详解(JUC)ReentrantLock

    上一节我们了解了Lock接口的一些简单的说明,知道Lock锁的常用形式,那么这节我们正式开始进入JUC锁(java.util.concurrent包下的锁,简称JUC锁).下面我们来看一下Lock最常用的实现类ReentrantLock. 1.ReentrantLock简介 由单词意思我们可以知道这是可重入的意思.那么可重入对于锁而言到底意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放.这模仿了 sy

  • java并发编程专题(三)----详解线程的同步

    有兴趣的朋友可以回顾一下前两篇 java并发编程专题(一)----线程基础知识 java并发编程专题(二)----如何创建并运行java线程 在现实开发中,我们或多或少的都经历过这样的情景:某一个变量被多个用户并发式的访问并修改,如何保证该变量在并发过程中对每一个用户的正确性呢?今天我们来聊聊线程同步的概念. 一般来说,程序并行化是为了获得更高的执行效率,但前提是,高效率不能以牺牲正确性为代价.如果程序并行化后, 连基本的执行结果的正确性都无法保证, 那么并行程序本身也就没有任何意义了.因此,

  • Java并发编程之阻塞队列深入详解

    目录 1. 什么是阻塞队列 2. 阻塞队列的代码使用 3. 生产者消费者模型 (1)应用一:解耦合 (2)应用二:削峰填谷 (3)相关代码 4.阻塞队列和生产者消费者模型功能的实现 1. 什么是阻塞队列 阻塞队列是一种特殊的队列,和数据结构中普通的队列一样,也遵守先进先出的原则同时,阻塞队列是一种能保证线程安全的数据结构,并且具有以下两种特性:当队列满的时候,继续向队列中插入元素就会让队列阻塞,直到有其他线程从队列中取走元素:当队列为空的时候,继续出队列也会让队列阻塞,直到有其他线程往队列中插入

  • Java 处理高并发负载类优化方法案例详解

    java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据) 一:高并发高负载类网站关注点之数据库 没错,首先是数据库,这是大多数应用所面临的首个SPOF.尤其是Web2.0的应用,数据库的响应是首先要解决的. 一般来说MySQL是最常用的,可能最初是一个mysql主机,当数据增加到100万以上,那么,MySQL的效能急剧下降.常用的优化措施是M-S(主-从)方式进行同步复制,将查询和操作和分别在不同的服务器上进行操作.我推荐的是M-M-Slaves

随机推荐