面试官:小伙子,说一下多线程异步编排和线程池吧

1.java内存模型

注 : JAVA中的堆栈和内存模型:,java

1.1内存模型:

Java内存模型是围绕着在并发过程当中如何处理原子性、可见性和有序性来创建的(三个特性详解见10.)
a.Java内存模型将内存分为了主内存和工做内存
b.Java内存模型规定全部的变量都存储在主内存中,每一个线程有本身的工做内存
c.主内存主要包括:堆和方法区,主内存是全部线程共享的
d.工做内存主要包括:该线程私有的栈和对主内存部分变量拷贝的寄存器(包括程序计数器和cpu高速缓存区)
e.Java内存模型规定了全部变量都存储在主内存中,每一个线程有本身的工做内存,线程的工做内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的全部操做都必须在本身的工做内存中进行,而不能直接读写主内存中的变量,不一样线程之间也没法直接操做对方工做内存中的变量,线程间变量值的传递须要经过主内存来完成,面试

1.2什么状况下线程栈中的数据会刷新呢主存中的变量?**

①当变量被volatile关键字修饰时,对于共享资源的读操做会直接在主内存中进行(固然也会缓存到工做内存中,当其余线程对该共享资源进行了修改,则会致使当前线程在工做内存中的共享资源失效,因此必须从主内存中再次获取),对于共享资源的写操做固然是先要修改工做内存,可是修改结束后会马上将其刷新到主内存中。数组

②经过synchronized关键字可以保证可见性,synchronized关键字可以保证同一时刻只有一个线程得到锁,而后执行同步方法,而且还会确保在锁释放以前,会将对变量的修改刷新到主内存当中。JVM规范定义了线程对内存间交互的八种操做:(待补充)缓存

1.3堆,栈,方法区

1.栈空间(stack),连续的存储空间,遵循后进先出的原则,存放基本类型的变量数据和对象的引用,但对象自己不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。); 当在一段代码块定义一个变量时,Java在栈中为这个变量分配内存空间,当该变量退出其做用域Ⅰ后,Java会自动释放掉为该变量所分配的内存空间,该内存空间能够当即被另做他用。
注:Ⅰ:变量的做用域:从变量定义的位置开始,到该变量所在的那对大括号结束
Ⅱ:变量周期性: 从变量定义的位置开始就在内存中活了;到达它所在的做用域的时候就在内存中消失了;多线程

2.堆空间(heap),不连续的空间,用于存放new出的对象,或者说是类的实例;当引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其做用域以外后被释放。而数组和对象自己在堆中分配,即便程序运行到使用 new 产生数组或者对象的语句所在的代码块以外,数组和对象自己占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不肯定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的缘由。
  实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针!并发

3.堆与栈:堆是由垃圾回收来负责的,堆的优点是能够动态地分配内存 大小,生存期也没必要事先告诉编译器,由于它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些再也不使用的数据。但缺点是,因为要在运行时动态 分配内存,存取速度较慢。app

栈的优点是,存取速度比堆要快,仅次于寄存器,栈数据能够共享(int a = 3再int b = 3此时内存中值存在一个3,a,b两个引用同时指向同一个3)。但缺点是,存在栈中的数据大小与生存期必须是肯定的,缺少灵活性。对于栈和常量池中的对象Ⅰ能够共享,对于堆中的对象不能够共享。栈中的数据大小和生命周期是能够肯定的。堆中的对象的由垃圾回收器负责回收,所以大小和生命周期不须要肯定 ,具备很大的灵活性。
注:Ⅰ:用new来生成的对象都是放在堆中的,直接定义的局部变量都是放在栈中的,全局和静态的对象是放在数据段的静态存储区,例如: Class People;People p;//栈上分配内存People* pPeople;pPeople = new People;//堆上分配内存
对于字符串:其对象的引用都是存储在栈中的,若是是 编译期已经建立好(直接用双引号定义的)的就存储在常量池中,若是是运行期(new出来的)才肯定的就存储在堆中 。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。异步

4.方法区(method),方法区在堆空间内,用于存放 ①类的代码信息;②静态变量和方法;③常量池(字符串常量和基本类型常量(public static final),具备共享机制);常量池指的是在编译期被肯定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各类基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,好比:类和接口的全限定名;字段的名称和描述符;方法和名称和描述符。
Java中除了基本数据类型,其余的均是引用类型,包括类、数组等等。ide

2.Java 线程

2.1.进程和线程的区别是什么?
线程是操做系统可以进行运算调度的最小单位也是进程中的实际运做单位。一个进程能够有不少线程,每条线程并行执行不一样的任务。不一样的进程使用不一样的内存空间,而当前进程下的全部线程共享一片相同的内存空间。 每一个线程都拥有单独的栈内存用来存储本地数据 .
性能

2.2线程的几种可用状态。

线程在执行过程当中,能够处于下面几种状态:
就绪(Runnable):线程准备运行,不必定立马就能开始执行。
运行中(Running):进程正在执行线程的代码。
等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
睡眠中(Sleeping):线程被强制睡眠。
I/O 阻塞(Blocked on I/O):等待 I/O 操做完成。
同步阻塞(Blocked on Synchronization):等待获取锁。
死亡(Dead):线程完成了执行。

2.3.建立线程的几种不一样的方式

有三种方式能够用来建立线程:

2.3.1继承 Thread 类 :经过继承Thread实现的线程类,多个线程间没法共享线程类的实例变量。

public class ThreadTest extends Thread {

    private int ticket = 10;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (this) {
                if (this.ticket > 0) {
                    try {
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName() + "卖票---->" + (this.ticket--));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    public static void main(String[] arg) {
        ThreadTest t1 = new ThreadTest();
        new Thread(t1, "线程1").start();
        new Thread(t1, "线程2").start();
        //也达到了资源共享的目的然而事实却不尽如此。
    }
}

2.3.2实现 Runnable 接口 : 这种方式更受欢迎,由于这不须要继承 Thread 类。在应用设计中已经继 承了别的对象的状况下,这须要多继承(而 Java 不支持多继承),只能实现接口。

public class RunnableTest implements Runnable {
    private int ticket = 10;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //添加同步快
            synchronized (this) {
                if (this.ticket > 0) {
                    try {
                        //经过睡眠线程来模拟出最后一张票的抢票场景
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName() + "卖票---->" + (this.ticket--));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] arg) {
        RunnableTest t1 = new RunnableTest();
        new Thread(t1, "线程1").start();
        new Thread(t1, "线程2").start();
    }
}

2.3.3 实现Callable接口 : 是Runnable接口的加强版,使用call()方法做为线程的执行体加强了以前的run()方法,由于call()方法有返回值,也能够声明抛出异常;

MyTask.java类

FutureTask使用方法:

Callable接口与Runnable接口对比:

1.Callable规定的方法是call(),而Runnable规定的方法是run().
2.Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
3.call() 方法可抛出异常,而run() 方法是不能抛出异常的。
运行Callable任务可拿到一个FutureTask对象, FutureTask表示异步计算的结果

3.线程池

3.1线程池介绍 :

线程池就是首先建立一些线程,它们的集合称为线程池。使用线程池能够很好地提升性能,线程池在系统启动时即建立大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束之后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

3.2线程池的工做机制 :

在线程池的工做模式下,任务是整个提交给线程池的,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,若是有,则将任务交给某个空闲的线程。一个线程同时只能执行一个任务,但能够同时向一个线程池提交多个任务。

3.3使用线程池的缘由 :

下降资源的消耗
经过重复利用已经建立好的线程下降线程建立和销毁带来的损耗
提升响应速度
线程池中的线程没有超过上限时,有线程处于等待分配任务的状态,当任务来时无需建立线程这一步骤就能直接执行。
提升线程的可管理性
线程池里提供了操做线程的方法,这就为对管理线程提供了可能性。

3.4四种常见的线程池详解 :

线程池的返回值ExecutorService: 是Java提供的用于管理线程池的类。该类的两个做用:控制线程数量和重用线程
咱们在实际业务中,以上三种线程启动的方式都不用。 将全部的多线程异步任务都交给线程池

3.4.1 原生线程池

建立
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();

原生线程池的七大参数
corePoolSize : 核心线程数(一致存在除非设置了allowThreadTimeOut),线程池建立好以后就等待来接受异步任务去执行。
maximumPoolSize : 最大线程数,控制资源并发
keepAliveTime : 存活时间,若是当前线程数量大于核心线程数,且线程空闲的时间大于指定的keepAliveTime就会释放线程(不会释放核心线程)
unit : 指定存活时间的时间单位
BlockingQueue workQueue : 阻塞队列的最大数量(该值的大小有压力测试后的峰值决定),若是任务数大于maximumPoolSize,就会将任务放在队列里,只要有线程空闲就会去队列里去除新的任务执行。
ThreadFactory threadFactory : 线程的建立工厂。
RejectedExecutionHandler handler : 若是workQueue满了按照指定的拒绝策略拒绝执行任务

运行流程
1.线程池建立,准备好core数量的核心线程,准备接受任务。
2.新的任务进来用core准备好的空闲线程执行。
(1)若是core满了,就将再进来的任务放入阻塞队列中,空闲的core就会本身去阻塞队列获取任务执行
(2)若是阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
(3)max任务都执行好了。Max减去core的数量的空闲线程会在keepAliveTime 指定的时间后自动销毁。最终保持到core大小
(4)若是线程数开到max的数量还不够用就是用RejectedExecutionHandler 指定的拒绝策略进行处理。
3.全部的线程都是由指定的factory建立
面试提

3.4.2种经常使用的线程池(返回值都是ExecutorService)

3.4.2.1 Executors.newCacheThreadPool():
可缓存线程池,core的数量为0,全部均可以回收, 先查看池中有没有之前创建的线程,若是有,就直接使用。若是没有,就建一个新的线程加入池中,缓存型池子一般用于执行一些生存期很短的异步型任务(运行结果见下汇总图).

线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程

3.4.2.2 Executors.newFixedThreadPool(int n):

建立一个可重用固定个数的线程池,core的数量为max,都不能够回收,以共享的无界队列方式来运行这些线程。(运行结果见下汇总图).

3.4.2.3 Executors.newScheduledThreadPool(int n):建立一个定长线程池,支持定时及周期性任务执行(运行结果见下汇总图).



3.4.2.4 Executors.newSingleThreadExecutor():

建立一个单线程化的线程池,从阻塞队列里挨个获取任务,它只会用惟一的工做线程来执行任务,保证全部任务按照指定顺序(FIFO , LIFO,优先级)执行(运行结果见下汇总图).

以上的全部execute均可以使用submit代替,而且submit能够有返回值

4.CompletableFuture异步编排

4.1建立异步对象

CompletableFuture提供了四个静态方法来建立一个异步操做。
Supplier supplier : 参数为一个方法
Executor executor能够传入自定义线程池,不然使用本身默认的线程池

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

给出一个例子

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService future= Executors.newFixedThreadPool(10);
        CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程------------------" + Thread.currentThread().getName());
            int i = 10 / 2;
            return i;
        }, future);
        //获取异步执行的结果在线程执任务行完以后返回
        Integer integer = integerCompletableFuture.get();
        System.out.println("结果为"+integer);//结果为5
    }
4.2whenComplete(计算完成时回调方法)

CompletableFuture提供了四个方法计算完成时回调方法

public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

下面给出一个例子

//方法执行完成后的感知
public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService future= Executors.newFixedThreadPool(10);
        CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程------------------" + Thread.currentThread().getName());
            int i = 10 / 0;//使用int i = 10 / 0;模拟有异常
            return i;
        }, future).whenComplete((ems,exception)->{
            //在出现异常时虽然能够感知异常但不能修改数据
            System.out.println("计算结果为:"+ems+"----异常为"+exception);
        }).exceptionally((throwable)->{
            //能够感知异常,并能够返回结果
            return 10;
        });
        Integer integer = integerCompletableFuture.get();
        System.out.println(integer);

    }

whenComplete能够感知正常和异常的计算结果,无异常时直接返回结果,在感知到异常时使用exceptionally处理异常状况
whenComplete和whenCompleteAsync的区别:
whenComplete : 是执行当前任务的线程执行继续执行whenComplete的任务
whenCompleteAsync :是执行把whenCompleteAsync这个任务继续提交给线程池来执行
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其余线程执行(若是是使用相同的线程池也可能会被同一个线程选中执行)

4.3handle方法(处理异常返回结果)

public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
 public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)
 public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)

下面给出一个例子

//方法完成后的处理
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService future= Executors.newFixedThreadPool(10);
        CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程------------------" + Thread.currentThread().getName());
            int i = 10 / 2;//使用int i = 10 / 0;模拟有异常
            return i;
        }, future).handle((result,throwable)->{
            if(result!=null){
                return result*2;
            }
            if(throwable!=null){
                return result*0;
            }
            return 0;
        });
        Integer integer = integerCompletableFuture.get();
        System.out.println(integer);
    }

和whenComplete同样,能够对结果作最后的处理(可处理异常),可改变返回值。

4.4线程串行化方法(完成上步执行其余任务)

public CompletableFuture<Void> thenRun(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor)
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor)
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

thenRun:处理完任务后执行thenRun后面的方法
thenAccept:消费处理结果。接受任务的执行结果并消费处理,无返回结果
thenApply:当一个线程依赖另外一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值
以上全部都要前置任务完成

给出一个例子

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService future= Executors.newFixedThreadPool(10);
        CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程------------------" + Thread.currentThread().getName());
            int i = 10 / 2;
            return i;
        }, future).thenApplyAsync(res -> {
            return res+3;
        }, future);
        Integer integer = integerCompletableFuture.get();//此处结果为8
        System.out.println(integer);
    }

thenRun不能获取到上一步执行结果
thenAccept:能接受上一步执行结果但没返回值
thenApply:既能接受上一步执行结果也有返回值

4.5将两任务组合-(都完成时,才执行做为参数传入的方法)

public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor)
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor)
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor)

给出一个例子

4.6将两任务组合-(一个完成,才执行做为参数传入的方法)

public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action)
public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor)
public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action,Executor executor)
public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn)
public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn,Executor executor)

给出一个例子

4.7多任务组合

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) 
    public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

allOf : 阻塞线程,等待全部任务完成,才继续往下进行不然阻塞
anyOf : 阻塞线程,只要有一个任务完成,就继续往下进行

给出一个例子

5.synchronized关键字与Lock

5.1 synchronized关键字:

Java 语言中,每一个对象有一把锁。线程可使用synchronized关键字获取对象上的锁。
同步代码块:
synchronized(锁对象){ 须要同步的代码 }
此处的所对象必须是存在堆中(多个线程的共享资源)的对象
同步方法:
权限关键字 synchronized 返回值 方法名(){ 须要被同步的代码块 }
同步方法的锁对象是this
静态方法及锁对象问题: 锁对象是类的字节码文件对象(类的class文件)

5.2锁的释放时机:

① 当前线程的同步方法、代码块执行结束的时候释放
② 当前线程在同步方法、同步代码块中遇到break、return 终于该代码块或者方法的时候释放。
③ 当前线程出现未处理的error或者exception致使异常结束的时候释放。
④ 程序执行了同步对象wait方法,当前线程暂停,释放锁。

5.3 lock和ReadWriteLock:

两大锁的根接口,Lock表明实现类是ReentrantLock(可重入锁),ReadWriteLock(读写锁)的表明实现类是ReentrantReadWriteLock。

5.3.1Lock相较synchronized的优势:

①synchronized实现同步线程(IO读文件时)阻塞不释放锁时,其余线程需一直等待,Lock能够经过只等待必定的时间 (tryLock(long time, TimeUnit unit)) 或者可以响应中断(lockInterruptibly())解决。

②多个线程读写文件时,读1操做与读2操做不会起冲突synchronized实现的同步的话也只有一个线程在执行读1操做.读2操做需等待,Lock能够解决这种状况 (ReentrantReadWriteLock)。

③能够经过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized没法办到的。

5.3.2lock中的方法: Lock lock = new ReentranLock;

lock();用来获取锁。若是锁已被其余线程获取,则进行等待;必须在try…catch…块中进行,而且将释放锁的操做放在finally块中进行,

*tryLock()😗尝试获取锁,获取成功返回true;获取失败(锁已被其余线程获取),返回false,这个方法不管如何都会当即返回(在拿不到锁时不会一直在那等待)

*tryLock(long time, TimeUnit unit)😗拿不到锁时会等待必定的时间,在时间期限以内若是还拿不到锁,就返回false,同时能够响应中断。拿到锁,则返回true。

*lockInterruptibly()😗当经过这个方法去获取锁时,若是其余线程正在等待获取锁,则这个线程可以响应中断,即中断线程的等待状态。也就使说,当两个线程同时经过lock.lockInterruptibly()想获取某个锁时,倘若此时线程A获取到了锁,而线程B只有等待,那么对线程B调用threadB.interrupt()方法可以中断线程B的等待过程。

interrupt()方法只能中断阻塞过程当中的线程而不能中断正在运行过程当中的线程。

unlock:释放锁在finally语句块中执行。

5.3.3ReadWriteLock中的方法:ReadWriteLock rl= new ReentrantReadWriteLock();**

维护了一对相关的锁,一个用于只读操做,另外一个用于写入操做。只要没有 writer,读取锁能够由多个 reader 线程同时保持,而写入锁是独占的。
rl.readLock();//返回Lock接口可经过Lock接口内方法获取锁
rl.writeLock();//返回Lock接口可经过Lock接口内方法获取锁

6.volatile

6.1volatile简介

volatile用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值当即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具有这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并非是线程A更新后的值)。volatile会禁止指令重排。

6.2volatile特性

volatile具备可见性、有序性,不具有原子性。

注意,volatile不具有原子性,这是volatile与java中的synchronized、java.util.concurrent.locks.Lock最大的功能差别,这一点在面试中也是很是容易问到的点。
原子性:原子性一般指多个操做不存在只执行一部分的状况,要么所有执行要么所有失败
可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程一、线程2…线程n可以当即读取到线程1修改后的值。
有序性:即程序执行时按照代码书写的前后顺序执行。在Java内存模型中,容许编译器和处理器对指令进行重排序,可是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

那么可能的一个执行顺序是:语句2 -> 语句1 -> 语句3 -> 语句4
那么可不多是这个执行顺序: 语句2 -> 语句1 -> 语句4 -> 语句3。
不可能,由于处理器在进行重排序时是会考虑指令之间的数据依赖性,若是一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2以前执行。重排序不会影响单个线程内程序执行的结果,但在多线程处理器不能保证

看完有什么不懂的欢迎在下方留言评论,都看到这了点个赞再走呗!

相关文章
相关标签/搜索