volatile、synchronized、ReentrantLock、Atomic原子类、CountDownLatch等

volatile关键字

volatile就是表示某人或某物是不稳定的、易变的

volatile与普通变量的区别

volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新

volatile保证内存可见性

1、可见性是指: 当一个线程修改了共享变量的值时,其他线程能够立即得到这个修改。

2、volatile实现内存可见性原理:

通过这条规则线程T对变量V的use动作是和load、read动作相关联的,必须连续且一起出现,使得每次使用V前必须先从主内存刷新最新的值。

通过这条规则线程T对变量V的assign动作是和store、write动作相关联的,必须连续且一起出现,使得更改后的值能立刻同步回主内存。

有序性-禁止指令重排序

volatile能在一定程度上保证有序性。

1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

例1:

//x、y为非volatile变量

//flag为volatile变量

x = 2;        //语句1

y = 0;        //语句2

flag = true;  //语句3

x = 4;         //语句4

y = -1;       //语句5

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。

并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。

例2:

//线程1:

context = loadContext();   //语句1

inited = true;             //语句2

//线程2:

while(!inited ){
    
    

  sleep()

}

doSomethingwithconfig(context);

而有可能语句2会在语句1之前执行,那么久可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。

这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,因为当执行到语句2时,必定能保证context已经初始化完毕。

不能保证原子性

volatile无法保证对变量的任何操作都是原子性的,比如i++。

synchronized关键字

synchronized:关键词,它依赖于JVM,保证了同一时刻只能有一个线程作用对象作用范围内进行操作。

1、内存可见性:

同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)”这条规则获得的。

2、操作的原子性:

原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。

  • synchronized修饰的代码块,作用于 调用的对象
  • synchronized修饰的方法,作用于 调用的对象
  • synchronized修饰的静态方法,作用于这个类的所有对象
  • synchronized修饰的,作用于这个类的所有对象

3、有序性

synchronized由”一个变量在同一时刻只允许一条线程对其进行lock操作“,来保证有序。
  
互斥区或临界区
synchronized(锁){

临界区代码

}

4、一个线程执行临界区代码过程:


    1 获得同步锁 
    2 清空工作内存 
    3 从主存拷贝变量副本到工作内存 
    4 对这些变量计算 
    5 将变量从工作内存写回到主存 
    6 释放锁

可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性

synchronized和volatile的比较

区别 Synchronized volatile
内存可见性 可以 可以
原子操作性 可以 不可以
是否会阻塞线程 synchronized可能会造成线程的阻塞 volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程
是否会被编译器优化 可以 volatile标记的变量不会被编译器优化
修饰 synchronized是一个方法或块的修饰符 volatile是变量修饰符,仅能用于变量

ThreadLocal

ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。

ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

CAS与AQS

CAS与AQS

ReentrantLock: CAS+AQS(非公平/公平)

ReentrantLock详细介绍

synchronized与ReentrantLock对比

1、可重入:

两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

2、锁的实现

对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。
而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。

3、性能的区别:

在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

4、功能区别:

便利性:
Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放;
而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。

锁的细粒度和灵活度:ReenTrantLock优于Synchronized

5、ReenTrantLock独有的能力:

  1. ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
  2. ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
  3. ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

CountDownLatch(倒计时锁): CAS+AQS

适用于一个线程,等待N个线程完成某个事情之后才能执行.

  • 用于控制线程的执行顺序
  • await():
    阻塞当前线程,并监视计数器的值,如果计数器值不为0则一直阻塞,如果计数器值变为0则自动让行,线程继续执行
  • countDown():
    计数器值减1

Semaphore(信号量): CAS+AQS(非公平/公平)

Semaphore信号量主要用于两个目的:
一个是用于多个资源的互斥作用,另一个用于并发线程数的控制。

无论是Synchroniezd还是ReentrantLock,一次都只允许一个线程访问一个资源,但是Semaphore可以指定多个线程同时访问某一个资源。

semaphore.acquire();
semaphore.release();

CyclicBarrier:ReentrantLock

让一组线程等待至某个状态之后再全部同时执行。

  • 线程间同步阻塞是使用的是ReentrantLock,可重入锁;
  • 线程间通信使用的是Condition,Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用。

AtomicInteger:CAS+volatile

unsafe实例

java.util.concurrent包里面的整数原子类AtomicInteger,其中的compareAndSet()和getAndIncrement()等方法都使用了Unsafe类的CAS操作。

 // setup to use Unsafe.compareAndSwapInt for updates

  //unsafe实例采用Unsafe类中静态方法getUnsafe()得到,但是这个方法如果我们写的时候调用会报错,
  //因为这个方法在调用时会判断类加载器,我们的代码是没有“受信任”的,而在jdk源码中调用是没有任何问题的

    private static final Unsafe unsafe = Unsafe.getUnsafe();

    private static final long valueOffset;

 

    static {
    
    

      try {
    
    

        valueOffset = unsafe.objectFieldOffset

            (AtomicInteger.class.getDeclaredField("value"));

      } catch (Exception ex) {
    
     throw new Error(ex); }

    }

    private volatile int value;//volatile关键字保证了在多线程中value的值是可见的,任何一个线程修改了value值,会将其立即写回内存当中

getAndIncrement 方法,该方法的作用相当于i++操作

getAndIncrement的功能为:
i与current比较,如果相等则把i的值变为next;
这时候可以保证在int next = current + 1;与if();之间不会被其他线程抢占(因为i的值在这段时间内没有变),如果被抢占则会做自旋操作。这就在某种程度上可以实现原子性操作。

这是一种不加锁而实现操作原子化的一种巧妙的编程方式,不仅在java的jvm种,甚至在操作系统的底层并发实现机制中也有CAS的大量应用。

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
    
    
        for (;;) {
    
    
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

猜你喜欢

转载自blog.csdn.net/amunamuna/article/details/107758074