java 面试知识点笔记(十二)多线程与并发-原理 中下篇

问:synchronized和ReentrantLock的区别?java

ReentrantLock(可重入锁)数组

  • 位于java.util.concurrent.locks包(著名的juc包是由Doug lea大神写的AQS抽象类框架衍生出来的应用)
  • 和CountDownLatch、FutureTask、Semaphore同样基于AQS实现
  • 可以实现比synchronized更细粒度的控制,如控制fairness
  • 调用lock()后,必须调用unlock()释放锁
  • 性能未必比synchronized高,而且也是可重入的

ReentrantLock公平性设置缓存

ReentrantLock fairLock = new ReentrantLock(true);

参数为ture时,倾向于将锁赋予等待时间最久的线程安全

公平锁:获取锁的顺序按前后调用lock方法的顺序(慎用,一般公平性没有想象的那么重要,java默认的调用策略不多会有饥饿状况的发生,与此同时若要保证公平性,会增长额外的开销,致使必定的吞吐量降低)多线程

非公平锁:获取锁的顺序是无序的,synchronized是非公平锁并发

例子:app

package interview.thread;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author: cctv
 * @Date: 2019/5/21 11:46
 */
public class ReentrantLockDemo implements Runnable {

    private static ReentrantLock lock = new ReentrantLock(false);

    @Override
    public void run() {
        while (true) {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " get lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

    }

    public static void main(String[] args) {
        ReentrantLockDemo rtld = new ReentrantLockDemo();
        Thread t1 = new Thread(rtld);
        Thread t2 = new Thread(rtld);
        t1.start();
        t2.start();
    }
}

公平锁 new ReentrantLock(true);框架

非公平锁 new ReentrantLock(false);jvm

ReentrantLock将锁对象化ide

  • 判断是否有线程,或者某个特定线程再排队等待获取锁
  • 带超时的获取锁尝试
  • 感知有没有成功获取锁

是否能将wait\notify\notifyAll对象化

  • java.util.concurrent.locks.Condition

总结synchronized和ReentrantLock的区别:

  1. synchronized是关键字,ReentrantLock是类
  2. ReentrantLock能够对获取锁的等待时间进行设置,避免死锁
  3. ReentrantLock能够获取各类锁信息
  4. ReentrantLock能够灵活的实现多路通知
  5. 机制:synchronized操做MarkWord,ReentrantLock调用Unsafe类的park()方法

 

问:什么是Java内存模型中的happens-before?

java内存模型(即Java Memory Model 简称JMM)是一种抽象概念,并不真实存在,它描述的是一组规则或规范,经过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式

JMM中的主内存

  1. 存储java实例对象
  2. 包括成员变量、类信息、常量、静态变量
  3. 属于数据共享区域,多线程并发操做时会引起线程安全问题

JMM中的工做内存

  1. 存储当前方法的全部本地变量信息,本地变量对其余线程不可见(工做内存是存储的主内存的变量的拷贝)
  2. 字节码行号指示器、native方法信息
  3. 属于线程私有的数据区域,不存在线程安全问题

JMM和java内存区域划分是不一样的概念层次:

  • JMM描述的是一组规则,围绕原子性、有序性、可见性展开
  • 类似点:都存在共享区域和私有区域

主内存和工做内存的数据存储类型以及操做方式概括:

  • 方法里的基本数据类型本地变量将直接存储在工做内存的栈帧结构中
  • 引用类型的本地变量:引用存储在工做内存中,实例存储在主内存中
  • 成员变量、static变量、类信息均会被存储在主内存中
  • 主内存共享的方式线程各拷贝一份数据到工做内存,操做完成后刷新回主内存

JMM如何解决可见性问题?

首先要讲下重排序:在执行程序时,为了提升性能,编译器和处理器经常会对指令作重排序。

指令重排序须要知足的条件:

  1. 单线程环境下不能改变程序运行的结果
  2. 存在数据依赖关系的不容许重排序(没法经过happens-before原则推导出来的,才能进行指令重排序)

happens-before的八大原则:

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操做先行发生于书写在后面的操做;
  2. 锁定规则:一个unLock操做先行发生于后面对同一个锁额lock操做;
  3. volatile变量规则:对一个变量的写操做先行发生于后面对这个变量的读操做;
  4. 传递规则:若是操做A先行发生于操做B,而操做B又先行发生于操做C,则能够得出操做A先行发生于操做C;
  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个一个动做;
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  7. 线程终结规则:线程中全部的操做都先行发生于线程的终止检测,咱们能够经过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

若是两个操做不知足上述任意一个happens-before规则,那么这两个操做就没有顺序的保障,JVM能够对这两个操做进行重排序

若是操做A happens-before B 那么操做A在内存上所作的操做对操做B都是可见的

 

volatile:jvm提供的轻量级同步机制,解决了内存可见性问题,但并非线程安全的(能够配合synchronized达到线程安全目的)

  • 保证被volatile修饰的共享变量对全部线程老是可见的
  • 禁止指令重排序优化

问:volatile变量如何当即可见?

当写一个volatile变量时,JMM会把该线程对应的工做内存中的共享变量刷新到主内存中

当读取一个volatile变量时,JMM会把该线程对应的工做内存置为无效,该线程只能从主内存中读取变量

问:volatile变量如何禁止重排优化?

内存屏障(Memory Barrier)

  1. 保证特定操做的执行顺序
  2. 保证某些变量的内存可见性

经过插入内存屏障指令禁止在内存屏障先后的指令执行重排序优化,强制刷出各类CPU的缓存数据,所以任何CPU上的线程都能读取到这些数据的最新版本

package interview.thread;

/**
 * 单例模式的双重检测实现
 *
 * @Author: cctv
 * @Date: 2019/5/21 17:19
 */
public class Singleton {
    // 禁止指令重排序优化
    private volatile static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //第一次检测
        if (instance == null) {
            //同步
            synchronized (Singleton.class) {
                // 第二次检测
                if (instance == null) {
                    // 多线程环境下可能会出现问题的地方(会出现指令重排序,致使instance先赋值后初始化Singleton)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

volatile和synchronized的区别

  • volatile本质是在告诉jvm当前变量在寄存器(工做内存)中的值是不肯定的,须要从主存中读取; synchronized则是锁定当前变量,只有当前线程能够访问该变量,其余线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可使用在变量、方法、和类级别的
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则能够保证变量的修改可见性和原子性
  • volatile不会形成线程的阻塞;synchronized可能会形成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量能够被编译器优化

问:谈谈 CAS(Compare and Swap)?

一种高效实现线程线程安全的方法

  • 支持原子更新操做,适用于计数器,序列发生器等场景
  • 属于乐观锁机制,号称lock-free(其实底层仍是有加锁)
  • CAS操做失败时由开发者决定是否继续尝试,仍是执行别的操做,因此失败线程不会阻塞挂起

cas思想:

包含三个操做数--内存位置V 预期原值A 和 新增B

  • 直接使用JUC的atomic包提供了经常使用的原子性数据类型以及引用、数组等相关原子类型和更新操做工具,是不少线程安全程序的首选
  • Unsafe类虽提供了CAS服务,但由于可以操纵任意内存地址的读写而有隐患
  • java9之后可使用Variable Handle API来替代Unsafe

缺点:

  1. 若循环时间长,则开销很大
  2. 只能保证一个共享变量的原子操做
  3. ABA问题(解决方法:AtomicStampedReference,它能够控制变量的版本保证正确性)
相关文章
相关标签/搜索