[java面试]之多线程

多线程

1、并行和并发的区别
  1. 并发是多个时间在同一时间段内执行(交替进行);交替做不同事情的能力;不同的代码块交替进行。
  2. 并行是多个时间在同一时间点内执行(同时进行);同时做不同事情的能力;不同的代码块同时进行。
2、浅谈对线程的理解
  • 计算机的核心是CPU,它承担了计算机所有的任务。可以把他比作一个工厂,时刻都在运行。
  • 假设这个工厂的能力有限,同时只可让一个车间进行生产,其他车间都停止。即单个CPU一次只可以执行一个任务。
  • 进程就像是工厂的车间,他代表CPU能够处理的单个任务,在任意时刻,CPU总是执行一个进程,其他进程都处于非运行状态。
  • 一个车间可以包括很多工人,他们协同完成这个车间应该完成的任务。线程就相当于是线程,一个进程中可以包括很多线程。
  • 车间的空间对工人是共享的,也就是说一个进程的内存空间是共享的,每个线程都可以使用这些共享空间。
  • 车间的容纳能力有限,有的车间只可以容纳一个工人进行生产,代表一个线程在使用某些共享空间时,其他线程必须等到该线程结束,才可以使用这一块的内存。
  • 防止其他线程进入该内存空间的方法,就是加锁,先到的线程上锁,后到的线程排队等候,这就是“互斥锁”。
  • 某些车间可以容纳几个线程,访问不了的线程可以在该内存空间外等待,这就是某些内存区域能够给固定数目的线程使用。
  • 防止多个线程不发生冲突的方法,可以加多个锁。
3、线程和进程的区别
  • 根本区别:进程是资源分配的最小单位;进程是CPU调度和分配的最小单位。
  • 包含关系:线程是进程的一部分,线程是轻权进程/轻量级进程。
  • 地址空间:同一进程中的线程,共享本进程中的地址空间,而进程之间则是独立的地址空间。
  • 内存分配:系统在运行时会为每一个进程分配不同的内存空间;而对线程而言,除了CPU,系统不会为线程分配内存,(线程使用的资源来源于所属进程的资源),线程之间只能共享资源。
  • 开销方面:,每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销,线程可以看做是轻量级的进程,同一线程共享代码和数据空间,每个线程都有自己独立运行的栈和程序计数器(PC),线程之间切换开销小。
  • 通信方面:线程之间的通信更为方便,同一进程下的线程共享全局变量,静态变量等数据。而进程之间的通信方式有多种(管道( pipe )、有名管道 (named pipe)、信号量( semophore )、消息队列( message queue )、信号 ( sinal )、共享内存( shared memory )、套接字( socket ))
  • 所处环境:在操作系统中能同时运行多个进程(程序),而在同一进程(程序)中有多个线程,同时执行(通过CPU调度在每个时间片中只有一个线程执行)。
  • 健壮性方面:一个进程崩溃之后,在保护模式下,不会影响其他进程,但是一个线程崩溃,整个进程都要死掉,所以多进程要比多线程健壮。
  • 执行过程:每个独立的进程都有一个程序运行的入口,可以独立运行,但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    10.并发:线程和进程均可并发执行。
4、守护线程是什么?

java线程分为用户线程和守护线程,守护线程(即daemon thread)是个服务线程,专门用于服务线程。
守护线程:比如垃圾回收线程,就是最典型的守护线程。
用户线程,就是应用程序里的自定义线程。

  • 守护线程专门服务于用户线程,用户线程执行完毕,main线程也就随之结束,jvm就会退出,守护线程停止。
  • 用户线程存在,jvm就不会退出,守护线程也不能退出,为了执行垃圾回收的任务。
  • java中把线程设置为守护线程的方法,在start线程之前,调用线程的setDaemon(true)方法,必须设置在start方法之前,否则会抛出异常(IllegalThreadSatateException)。
  • 守护线程创建的线程也是守护线程。
  • 当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则不会退出。(以上是针对正常退出,调用System.exit则必定会退出)
5、创建线程的几种方式
  • 继承Thread类,创建线程类。
    1.1定义Thread类的子类,并重写该类的run方法,该run方法的方法体,就代表了线程要完成的任务,因此把run()方法称为执行体。
    1.2创建Thread类的子类,即创建了线程对象。
    1.3调用线程的start()方法来启动该线程。
  • 实现Runnable接口,创建线程类。
    2.1定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    2.2创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    2.3调用线程对象的start()方法来启动该线程。
  • 实现Callable和Future来创建线程
    3.1创建Callable接口的实现类,并实现Call()方法,该call()方法作为线程的执行体,并且有返回值。
    3.2创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象,封装了该Callable对象的call()方法的返回值。
    3.3使用FutureTask对象作为Thread对象的target创建并启动新线程。
    3.4调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
6、Runnable和Callable的区别
  • 相同:都是接口,都可以编写多线程程序,都是采用Thread.start()方法来启动线程。

  • 不同:1、Runnable的run()方法没有返回值
    Callable可以返回执行结果,支持泛型。
    2、Runnable接口的run方法只能抛出运行时异常且无法捕获处理,Callable接口call方法允许抛出异常,可以获取异常信息。
    3、Callable接口支持返回执行结果,需要调用FutureTask.get()方法得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

7、线程有哪些状态
  • 新建、就绪(可运行)、运行、阻塞(等待/睡眠)、死亡。
    在这里插入图片描述
    1、线程创建好之后,调用start()方法执行后,,并不是表示线程已经开始运行了。而是进入了就绪状态(可运行),随时准备运行,但是真正何时运行,由操作系统决定,代码并不能控制。
    2、从运行状态的线程,也可能是由于失去了CPU资源,回到就绪状态,也是由操作系统决定的,这一步也可以由程序主动失去CPU资源,只需调用yirId方法。
    3、线程运行完毕或运行到一半异常了,或者是主动调用了线程的stop()方法,则进入死亡状态,死亡状态的线程不可逆转。
    4、引起线程阻塞的几种行为:
    4.1主动调用了sleep方法,时间到了就会自动进入就绪状态。
    4.2主动调用了suspend方法,主动调用resume()方法,就会进入就绪状态。
    4.3调用了阻塞式IO方法,调用完成后进入就绪状态。
    4.4试图获取锁,成功的获取锁之后,就会进入就绪状态。
    4.5线程在等待某个通知,其他线程发出通知后,会进入就绪状态。
8、有关线程的挂起
  • 什么是线程的挂起?
    线程的挂起操作实质上是指线程进入到“非可执行”状态下,在这个状态下,CPU不会分配给线程时间片,进入到这个状态,可以暂停一个线程的运行。
  • 为什么要挂起线程?
    CPU分配的线程片非常短,同时也是非常珍贵的,为了避免资源的浪费,才有了挂起线程。
  • 线程挂起的方法:
    1、废弃的方法:Thread.suspend(),该方法不会释放线程所暂用的资源,如果使用该方法挂起线程,可能会使其他等待资源的线程锁死。
    2、常用的方法:
    wait()暂停执行,放弃已获得的锁,进入到等待状态。
    notify()随机唤醒一个等待的线程。
    notifyall()唤醒所有在等待锁的线程,自行抢占CPU。
  • 什么时候使用挂起线程?
    线程在等待某些未就绪的资源时,先释放当前锁,避免资源浪费,等待的资源就绪后调用notif方法唤醒线程。
9、sleep()和wait()的区别?
  1. sleep方法属于Thread类中的静态方法,wait方法属于Object的成员方法。
  2. sleep没有释放锁,wait方法释放了锁,使得其他线程可以同步代码块或者方法。
  3. wait、notify、notifyall只能在同步控制方法或者同步代码块中使用,而sleep可以在任何地方使用。
  4. 概括:
    sleep方法属于Thread类中方法,表示让一个线程进入到睡眠状态,等待一定的时间之后,会自动进入到就绪状态,并不会马上进入到运行状态,因为线程调度机制是,恢复线程的运行也需要一定的时间,一个线程对象在调用了sleep()方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响到其他进程对象的运行,但是在sleep的过程中,有可能被其他的对象调用它的interrupt()方法产生InterruptException异常,如果线程不能捕获这个异常,那么线程就会终止,进入到TERMINATED结束状态,如果程序捕获了这个异常,那么程序就会继续执行catch语句块,可能还有finally语句块以及之后的代码。
    wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要使用notify()方法和notifyall()方法来唤醒线程,如果线程拥有某个或者某些对象的同步锁,那么在调用wait()方法后,这个线程就会释放他所持有的所有的同步资源,wait方法也同样可能会在wait的过程中调用了interrupt()方法而产生了InterruptException异常,效果及处理方式同sleep一样。
10、notify()和notifyall()的区别?

首先要先了解锁池和等待池。
锁池:假设线程A已经拥有了某个对象的锁,而其他对象想要调用这个对象的某个synchronized方法(或者是synchronized块)由于这些线程在进入到对象的synchronized方法之前必须先获得对象的锁的拥有权,但是该对象的锁目前正被A拥有,所以这些对象就进入到了该对象的锁池中。
等待池:假设一个线程调用了某个对象的wait方法,线程就会释放该对象的锁,然后进入到该对象的等待池中。

  1. 线程调用了wait方法,那么线程便会进入该对象的等待池中,等待池中的线程不会竞争该对象的锁。
  2. 当线程调用了对象的notifyall或者notify方法,这时就会唤醒线程。
    2.1notify只随机唤醒一个wait线程,具体是唤醒哪个线程和线程调度器有关。即调用了notify方法后,只有一个线程会由等待池进入到锁池中。
    2.2notifyall会唤醒所有wait线程,他会将对象等待池中的所有线程都移动到锁池中,等待锁竞争。
  3. 优先级高的线程竞争到锁的概率大,假如某个线程未竞争到该对象锁,那么他还会留在锁池中,只有线程再次调用了wait方法,他才会重新回到等待池中,而竞争到对象锁的线程则继续往下执行,知道执行完synchronized代码块,他就会释放对象锁,这时锁池中的线程则会继续竞争该对象锁的拥有权。
  4. notify可能导致死锁,而notifyall不会导致死锁。
11、线程的run()方法和start()方法的区别
  1. start()方法时启动线程,使线程进入就绪状态,start方法不能重复调用。
  2. run()方法是一个普通的成员方法,直接调用在当前线程中处理run()方法的主题,可以重复调用。
  3. 通常,系统通过调用线程类的start()方法来启动一个线程,此时线程就会处于就绪状态,而不是进入运行状态,也就意味着这个线程可以被jvm来调度。在调度过程中,jvm会通过调用线程类的run()方法来完成试机操作,当run()方法结束之后,此线程就会终止。
  4. 也可以不通过start()方法直接调用run()方法,如果直接调用线程类的run()方法,它就会被当做一个普通的函数调用,程序中任然只有主线程这一个线程。也就是说,start()方法可以异步地调用run()方法,但是直接调用run()方法确实同步的,因此也就不能达到多线程的目的。
12、创建线程池有哪几种方式?
  1. 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复多次使用,省去了频繁创建线程的操作,减少资源的消耗。
  2. NewSingleThreadExecutor:创建一个单线程的线程池,只有一个线程工作,保证所有任务的执行顺序按照任务的提交顺序执行。
  3. NewFixedThreadPool:创建固定大小的线程池,每次提交一个任务,就从线程池中拿一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就不会发生改变,如果某个线程因为执行异常而结束,那么线程池会补充一个新的线程。
  4. NewCachedThreadPool:创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程,当任务增加时,此线程又可以智能的添加线程来处理任务。
  5. NewSchedThreadPool:创建一个支持定时以及周期性执行任务的线程池。
13、线程池都有哪些状态?
  1. RUNNING:这是最正常的状态,接收新的任务,处理等待队列中的任务,线程池的初始状态是RUNNING,线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数量为0。
  2. SHUTDOWN:不接收新的任务提交,但是会继续处理等待队列中的任务,调用线程池的shutdown()方法时,线程池由RUNNING变成SHUTDOWN。
  3. STOP:不接收新的任务提交,不再等待队列中的任务,中断正在执行任务的线程,调用线程池的shutdownNOW()方法时,线程池的状态变化为(RUNNING或者SHUTDOWN)变成STOP。
  4. TIDTING:所有任务都销毁了,workConut为0,线程池的状态在转换为TIDTING状态时,会执行钩子方法.terminated(),因为.terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池中变成TIDYING时进行相应的处理,可以通过重载terminated()函数来实现。
    当线程池在SHUTDOEN状态下,阻塞队列为空并且线程池中执行的任务为空时,就会由SHUTDOEN变成TIDYING。
    当线程在STOP状态下,线程中执行的任务为空时,会有STOP变成TIDYING。
  5. TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由TIDYING变成TERMINATED。
14、线程池中submit()和execute()方法的区别
  1. 如果需要返回结果的线程调用,用submit()方法,获取Future对象,调用get()获取返回值,可以捕获异常。
  2. 如果不需要返回值,使用execute()方法,效率高执行快,但是无法捕获异常。
15、java程序如何保证多线程的安全运行?
  1. 原子性:提供互斥访问,同一时刻只能有一个线程对数据操作。

  2. 可见性:一个线程对主内存的修改,可以及时地被其他线程看到。

  3. 有序性:一个线程观察其他线程的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序。

  4. 在保证线程的原子性,可见性和有序性的前提下,即可保证线程安全运行。

  • 对应的解决方案:
    1、JDK Atomic开头的原子类,synchronized. lock可以解决原子性问题。
    2、synchronized. Volatile. Lock可以解决可见性问题。
    3、Happens-Before 规则可以解决有序性问题。
16、多线程锁的升级原理

在这里插入图片描述

  1. 无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只能有一个线程修改成功,其他修改失败的线程会不断的重试,直到修改成功。

  2. 偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续执行中自动获取锁,降低了获取锁时带来的性能开销,偏向锁指的是偏向第一个加锁线程,该线程不会主动释放偏向锁,只有其他线程尝试竞争偏向锁才会释放。
    偏向锁的撤销需要在某个时间节点上没有字节码正在执行时,先暂停偏向锁的线程,然后判断锁对象是否处于被锁定状态,如果线程不处于活动状态,则将对象设置成无锁状态,并撤销偏向锁。
    如果线程处于活动状态,升级为轻量级锁。

  3. 轻量级锁:当锁为偏向锁时,被第二个线程B访问此时偏向锁会升级为轻量级锁,线程B会通过自旋的形式尝试获取锁,线程不会阻塞,从未提升性能,自旋超过一定的次数,轻量级锁升级为重量级锁。
    当一个线程已持有锁,另一个线程在自旋,第三个线程来访问时,轻量级锁则会升级为重量级锁。
    原因:未优化之前,synchronized是重量级锁(悲观锁),使用wait、notify、notifyall来切换线程状态非常消耗系统资源,线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。所以jvm对synchronized关键字进行了优化,把锁分为了无锁、偏向锁、轻量级锁和重量级锁。

17、乐观锁和悲观锁
  1. 悲观锁:当线程每次拿到数据时都会上锁,这样别的线程就会阻塞,知道它拿到锁(共享资源每次只给一个线程使用,其他线程会阻塞,用完后再把资源转让给其他线程)。
  2. 乐观锁:乐观的认为每次拿到数据的时候,都不会被修改,因此不会上锁,但是在更新的时候会判断一下,在此期间,是否有更新过该数据可以使用版本号机制和CAS算法实现。
18、什么是死锁,产生原因,如何防止?
  • 死锁是指两个或者两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信造成的阻塞现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或者是说系统产生了死锁。这些永远在等待的进程称为等待进程。
  • 产生原因:
    1、竞争资源发生死锁现象,系统中供多个进程共享的资源的数目不足以满足全部进程的需要时,就会引起对诸资源的竞争而发生死锁现象。
    可剥夺资源:某进程在获得该资源时,该资源同样可以被其他进程或系统剥夺。
    不可剥夺资源:系统把该类资源分配给某个进程时,不能强制收回,只能在该进程使用完成之后自动释放。
    2、进程推进顺序不当导致死锁。
    产生死锁的四个必要条件:
    2.1互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有的资源使用完成之后释放了该资源。
    2.2请求和保持条件:进程获得了一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时,请求阻塞,但又对自己获得的资源保持不放。
    2.3不可剥夺条件:是指进程已获得的资源在未完成使用之前,不可被剥夺,只能在使用后自己释放。
    2.4环路等待条件:是指进程发生死锁后,必然存在一个进程—资源之间的环形链。
    2.5这四个条件是产生死锁的必要条件,只要系统发生死锁,这些条件必然成立。只要上述一个条件不满足那么就不会发生死锁。
19、死锁的避免和预防

基本思想:系统会对进程发出每一个系统都能够满足的资源申请,进行动态检查,并且根据检查的结果,决定是否分配资源,如果分配后可能发生死锁,则不予分配,否则会予以分配,这是一种保证系统不进入死锁状态的动态策略。
只要打破四个必要条件之一即可有效预防死锁:

  • 打破互斥条件:改造独占资源为虚拟资源,但大部分资源无法改造
  • 打破不可剥夺条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原来占有的资源。
  • 打破占有申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不满足则等待,这样就不会占有且申请。
  • 打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。
20、ThreadLocal是什么,使用场景有哪些?

ThreadLocal是用来维护线程中变量不能被其他线程干扰而出现的一个结构(但是并不能解决共享变量的并发问题)。ThreadLocal是个线程将值存入该线程的map中,以ThreadLocal自身作为key,需要时获得的是该线程之前存入的值,如果存入的是共享变量,那么取出的也是共享变量。
经典的使用场景是为每一个线程分配一个JDBC连接Connection,这样就能保证每个线程都在各自的Connection上进行数据库的操作,不会出现A线程关了B线程正在使用的Connection。
还有使用场景有:Session管理等等。

21、synchronized锁的实现原理。

synchronized的三种应用方式:

  1. 修饰实例方法:作用于当前实例加锁进入同步代码块前要获得当前实例的锁。
  2. 修饰静态方法:作用于当前类对象加锁进入同步代码块前要获得当前类对象的锁。
  3. 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前获得给定对象。

synchronized的作用主要有三个:

  1. 确保线程互斥的访问同步代码块。
  2. 保证共享变量的修改能够及时可见。
  3. 有效解决重排序问题。

synchronized实现原理:是基于进入和退出Monitor对象实现。
每一个对象都有一个监视器锁(monitor)当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时获取monitor的所有权。
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor只是重新进入,则进入monitor的进入数为1。
3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
4、monitor退出,执行monitorexit的线程必须是objectref所对应的monitor的所有者。
5、执行指令时,monitor的进入数减1,如果减1后,进入数为0,那么线程退出monitor,不再是这个monitor的所有者,其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。
6、通过进去和退出的描述,synchronized的语义底层是通过一个monitor的对象完成的,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.MonitorStateException的异常的原因。

22、Volatile和synchronized的区别
  1. Volatile本质是告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞。
  2. volatile只能使用在变量级别,synchronized则可以用在变量、方法和类级别。
  3. volatile仅能实现变量的修改可见性,不能保证原子性,而synchronized则可以保证变量的修改可见性和原子性。
  4. volatile不会造成线程阻塞,而synchronized可能会。
  5. volatile标记的变量不会被编译器优化,synchronized标记的变量可以被编译器优化。
23、synchronized和lock的区别
  1. lock是一个接口,synchronized是一个关键字。
  2. synchronized会自动释放锁,而lock必须手动释放锁。
  3. lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
  4. 通过lock可以知道线程有没有拿到锁,而synchronized不能。
  5. lock能提高多个线程读写操作的效率。
  6. synchronized能锁住类,方法和代码块,而lock是块范围内的。
24、synchronized和ReentrantLock的区别
  • 相似点:他们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而线程阻塞和唤醒的代价比较高。
  • 区别:
    1、API层面:这两种方式最大的区别就是对于synchronized来说,他是java语言类的关键字,是原生语法层面的互斥,需要jvm实现,而ReentrantLock是JDK1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。
    2、等待可中断:是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,可等待特性对处理执行时间非常长的同步块很有帮助。
    使用synchronized,如果线程A不释放,线程B一直等待,不能被中断。
    使用ReentrantLock,如果线程A不释放,线程B等待时间久了,可以中断等待,转而去做别的事情。
    3、公平锁:是指多个线程在等待同一个锁时,必须按照申请的时间顺序来,依次获得锁,而非公平锁则不能保证这一点,非公平锁在锁被释放时,任何一个等待线程都有机会获得锁。
    synchronized的锁是非公平锁,ReentrantLock默认情况下,也是非公平锁,但是可以通过带布尔值的构造函数要求使用公平锁。
    4、锁绑定的多个条件:
    ReentrantLock可以同时绑定多个Condiition对象,只需多次调用newCondition方法即可。
    synchronized中,锁对象的wait()和notify()或notifyall()方法可以实现一个隐含的条件,但要和多于一个的条件关联时,就不得不额外添加一个锁。
25、atomic的实现原理

atomic的作用:多线程下将属性设置为atomic可以保证读取数据的一致性,因为他将保证数据只能被一个线程占用,也就是说一个线程对属性进行写操作时,会使用自旋锁,锁住该属性,不允许其他的线程对其进行读取操作。
但是他有一个很大的缺点:因为他要使用自旋锁锁住该对象,因此他会消耗更多的资源,性能会很低,要比nonatomic慢20倍。
原理:

  1. 直接操作内存,使用Unsafe这个类。
  2. 直接getIntVolatile(Var1,Var2)获取线程间共享的变量。
  3. 采用CAS的尝试机制(核心所在)。
  4. 使用Atomic是在硬件上,寄存器阻塞,而不是在线程,代码阻塞上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值